From 6e143f7ed31f0928519e0c83bf56dca1de7a3f19 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 27 May 2020 14:54:27 +0300 Subject: [PATCH 1/4] Apple OAuth tests --- app/config/providers.php | 12 ++++++------ public/images/oauth2/apple.png | Bin 1360 -> 3582 bytes src/Appwrite/Auth/OAuth2/Apple.php | 29 +++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/app/config/providers.php b/app/config/providers.php index 703bbef26..490c17355 100644 --- a/app/config/providers.php +++ b/app/config/providers.php @@ -73,12 +73,12 @@ return [ 'enabled' => true, 'mock' => false, ], - // 'apple' => [ - // 'developers' => 'https://developer.apple.com/', - // 'icon' => 'icon-apple', - // 'enabled' => false, - // 'mock' => false, - // ], + 'apple' => [ + 'developers' => 'https://developer.apple.com/', + 'icon' => 'icon-apple', + 'enabled' => true, + 'mock' => false, + ], 'amazon' => [ 'developers' => 'https://developer.amazon.com/apps-and-games/services-and-apis', 'icon' => 'icon-amazon', diff --git a/public/images/oauth2/apple.png b/public/images/oauth2/apple.png index b0fdfd14f055970a22144a33cef8f0d79747b60d..98086e8048cce1c1a8d8317bb47394ac48bc6d40 100644 GIT binary patch literal 3582 zcmbVP3p|s1A72-aT%uC>s5B!7HM=m&+BldPrn%FFW_!k%ZEX+c5-HVjNzqHWox)r0 zVR96T2uBN*=$xaw3OR9dDI%R`<8*oJyzl4pem>8$XaE2Acm4i~tNg~p<=SR^EY6ozv_x(LY?>PSt@+aLo%hJein**q>> zYLo8A3js+81cbvEi~}O}C+l3{Li!L9C@F%%7@<+0i}M3`AWs;;52LnF2|Act5nO^}?QaB;fs&ePDAsVqV&oUjaPZ$Y-$3pci ztu-0s{Y?luXaW`L2qqn*lj$H7D;AAKBhfe{*3=DaOu(8FjLkNqjR_0H$mDrUHY@yp zNlUu`PK4yCRDvB_2=WBsvaR{na0T|wujZE=wyYq-f245RR(meWqgfy@Cc}U;ld?6$vN;*IOCXI;tnpPr{K>*nx2l$L+ zOAh#DM$(}};Ly4LB!mdb1X%PC4u~+P^Z6WL-UEldpf2}Z|x$$|)&t@B>GQS7S zOlbI`K2Bd~@o|0wTxe(upy@oNYnuauEsAAB{qa>_z+n0*At_y($s%3X?*@TgC7gI7a3~u~BKw2GI z|Ee-5&Ejo&PwUeoimQ8?nv2h8HLYrL4LZ*nPv%!;CR~r}in~&~n>N+io4>r__iS*g zk3~{nFXx$GkviLQ+qd-mL{de4cCurHJMv*$G-VHQ8-8=;o_du-@cyN=)6692*$Iqp z)fugKLusplvHgc<*&e!{Y2G?VJFyg((;}NaX)pK1FT353z4Ppkxeu>OQZxp`itoua zj&r<6b7<8n(@4oXg1B@RbLq@u?5-QWW^vb4Z`auGY+Gc)X`FVaB#tz-=v^cRt{}Ob zxc~G5dGSN1TyteAo>nzDQF^JXeMwyJ^O6g6-p1NzQO$Q$!%_Ytmhp>fpx5j&zMZ!a z22)xky%)hUvo&Bac_Rwh(oK~8s?Wi3)h>)OGmHrvY@F4rM?B?zKUr7@V{(=+46Pj&Nzlr5?lY zXWkY#Dl+are8|OSoQ=GjR9svP5=ekcety1f@_M?4e64Npa>zOT>8Y{nOZ@+}zx$*<^ zlJv>DJb|}U9q~R#v*EpmJZ}NwTVj~aMv!49SBeEN4LHfMKPJr z`f7M7cFd@#-9vLdm)z(YtXr@T$$Lw1ahk55R*mXboj<&CH9Atac{0?;NaEL;Km=nH||%p9n6Ce)9MBDCi!VjIdO@A(5=p)YRii z9Cb95wFqEgMFlN)*j>&uy`4#jeDg=_lJ6816v$wlYmg|GNF+gA?h&VDjHFGJTG8vLO^20ob=tXY$r~V62>M$j zW1TtM2Htg@HB9a&Y}=N5sOgOP{^G*I1IrnD->GWFEmveD1qB6F&shXuGafd?sIFUO zHF)Qo!!63qrq*NYY@Fj%N=iy39MjEsTR%z9tI%fdUcV&XgWAddHr3}x;A_&``eWap zI{{qI&$qs<{Uen(`LgHV_RsaSjzd2-HxrgtRwN`QQqpd8JbwK6mtTI&vsP0osi-*N zR&;;q;zj3X-Qn=b7YN+spx z$K8sugCAYJA{M(`es{fTZ07yRGpPq}4Nuna9(jz^<0~r`$2MNK4)t<4WkiaZt6#Hx zyJH)xYI0(tam1ghv0+1SVBq40_nDT~)=NNIjFm!lbv654+Ci~cTv>@3JUjR1N%PVD zGjG^neC3vzcauSbKSrlG>Ma{lNWI@jR>0wKv5$0}SSFgyPtZkyarlF0NRe-*$Nj~) z7+XRPm(A98KgRdkXb~MXJ+|q$%m#)?G}V&0_SPPzr;pE1uR0Va_gvyz4x{{bUlzy_ zHYH)wb%%WajNU<^P~d6@sD?RC8@Gij6XL!1dcV0_w61}hLRE` z!y+cu+tqbR&b8g8UX^|U0SdaM!hz0Miytzo@@r z(LVh-HOthpYn!KXGckOOoHKr@$SqKkj=W_&M{+9Y9vC;saYLkRjh?-fx5|0wrH5J^ zR8u!!1Ye`+KO3KxmzQ4|ANPR!Rzw`WSB}>_)*Yjy?dI$I=+|GjnwoAe1ZatCkhP(& zcA98*>{idyH;W&&#uA`xfZa|s@zQLM}ex+zikv7C-McWy$AmSpB2DC delta 1352 zcmV-O1-JVC8_)`n8Gi%-007$SUEcrz00d`2O+f$vv5yP zfP?@5`Tzg`fam}Kbua(`>RI+y?e7jT@qQ9J+u00Lr5M??VshmXv^00009a7bBm z0001B0001A0I0QS^8f$<2XskIMF-;w1r#pk->6#`@lQg-$jFDzomvg?&RXCsflpp7u@8{fm&bjA%FBl94 zgTY`h7z_r3!GB;d>>!S+x9B9m{WRm{ccOOdtKz`VGxQVVDp^iQR_(5$hMNdSIYJnK zJXRIk7R1A0zNUzE{mAf<-65%IJjq~*hQ%^(+8tEoI0^F|g%S;mNsibZR;4)b(@$=T zMvhb5ZFgXm;^rx?Y|*fY^QzrpRgRZqW#=QyY3{K*tba;z65;ZeVQw)+uMJDFCLUfQ zRU%PjmRImdH?#8#qLY0ktrWP#yBsI~{}XZGyKxz%`JVSUM@qgKd68N?q$%F`Y>@+s z%reLTzc7xIT{Ph)%`z);{Uw8kR=R1Y2`_Ha%rilZNwPS3kOSmcV1^7~x@n^sA1<;a zS!9YKMt@lQFNnB^(8pth@KJ{gD3W271!8>55K9!PB}5Os?57bgw_R`7B2SVyL!96T z*6kJX@-oj8#*6c&-%>2Gz-3lwVGp|r{KIUu!7L}~Coa&firmNB%$2PYSaGWLiBX%ZA!WK6#jH4z_o z2!D#CNZ2J(FS_T>OKg1AL>!_y5SPTnp(au&*~;ou%k6fFtdkQI@k=D@)I`?wwxwmX z&|{a#B3A@O{5+z+E~$ykvnVJ69rW5G5*HQ;@d6ROD%2*v0$p?qCiff!sb@qd1F1TL z-%B?>F%j_4Mk7IQ+JD)@H4D1-G!SH*iDVD`FlAIwX*)Nr_kI&0q27gdL zq8zYG1K?@Si)c*oqR#c9zgzy38Y0?Rl~q3B1JQk_0*!n~TI+0N`O?0}ZlxULG+AYh zA~F4ql@y0KyUn1_lG)~g=@`%I=gH*qQ%~?Zk5garjm2LKGfjdtMe6ahmo7qEN^KD1 zeZHWe+q~S3c$BML;4oq8fCMuP^M515%#p?d9MloyVR|@7D-F0b>ppnF*z+UFfA}MR53Iuser; } + + protected function getToken($p8) + { + $keyfile = 'AuthKey_AABBCC1234.p8'; # <- Your AuthKey file + $keyid = '4LFF7TZ6Q5'; # <- Your Key ID + $teamid = 'YJHMCSNREU'; # <- Your Team ID (see Developer Portal) + $bundleid = 'test2.appwrite.io'; # <- Your Bundle ID + $url = 'https://api.development.push.apple.com'; # <- development url, or use http://api.push.apple.com for production environment + $token = 'e2c48ed32ef9b018........'; # <- Device Token + + function base64($data) { + return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '='); + } + + $message = '{"aps":{"alert":"Hi there!","sound":"default"}}'; + + $key = openssl_pkey_get_private('file://'.$keyfile); + + $header = ['alg'=>'ES256', 'kid'=>$keyid]; + $claims = ['iss'=>$teamid, 'iat'=>time()]; + + $header_encoded = base64($header); + $claims_encoded = base64($claims); + + $signature = ''; + openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256'); + $jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature); + + } } From 7296db0d06eae64f2498bba63c462f6041d6523c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 29 May 2020 15:02:53 +0300 Subject: [PATCH 2/4] Added UI adjutments for Apple unique secret key --- app/app.php | 1 + app/config/providers.php | 21 +++ app/controllers/api/account.php | 24 ++++ app/controllers/api/projects.php | 2 +- app/views/console/users/index.phtml | 36 +++-- gulpfile.js | 1 + public/dist/scripts/app-all.js | 8 +- public/dist/scripts/app.js | 8 +- public/scripts/views/forms/oauth-apple.js | 94 +++++++++++++ src/Appwrite/Auth/OAuth2/Apple.php | 158 +++++++++++++++------- 10 files changed, 286 insertions(+), 67 deletions(-) create mode 100644 public/scripts/views/forms/oauth-apple.js diff --git a/app/app.php b/app/app.php index 9fba836c2..a72116a76 100644 --- a/app/app.php +++ b/app/app.php @@ -104,6 +104,7 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $ if(!$originValidator->isValid($origin) && in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE]) + && $route->getLabel('origin', false) !== '*' && empty($request->getHeader('X-Appwrite-Key', ''))) { throw new Exception($originValidator->getDescription(), 403); } diff --git a/app/config/providers.php b/app/config/providers.php index 490c17355..1a6b067ec 100644 --- a/app/config/providers.php +++ b/app/config/providers.php @@ -5,30 +5,35 @@ return [ 'developers' => 'https://developer.atlassian.com/bitbucket', 'icon' => 'icon-bitbucket', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'facebook' => [ 'developers' => 'https://developers.facebook.com/', 'icon' => 'icon-facebook', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'github' => [ 'developers' => 'https://developer.github.com/', 'icon' => 'icon-github-circled', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'gitlab' => [ 'developers' => 'https://docs.gitlab.com/ee/api/', 'icon' => 'icon-gitlab', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'google' => [ 'developers' => 'https://developers.google.com/', 'icon' => 'icon-google', 'enabled' => true, + 'form' => false, 'mock' => false, ], // 'instagram' => [ @@ -41,6 +46,7 @@ return [ 'developers' => 'https://developer.microsoft.com/en-us/', 'icon' => 'icon-windows', 'enabled' => true, + 'form' => false, 'mock' => false, ], // 'twitter' => [ @@ -53,90 +59,105 @@ return [ 'developers' => 'https://developer.linkedin.com/', 'icon' => 'icon-linkedin', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'slack' => [ 'developers' => 'https://api.slack.com/', 'icon' => 'icon-slack', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'dropbox' => [ 'developers' => 'https://www.dropbox.com/developers/documentation', 'icon' => 'icon-dropbox', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'salesforce' => [ 'developers' => 'https://developer.salesforce.com/docs/', 'icon' => 'icon-salesforce', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'apple' => [ 'developers' => 'https://developer.apple.com/', 'icon' => 'icon-apple', 'enabled' => true, + 'form' => 'apple.phtml', 'mock' => false, ], 'amazon' => [ 'developers' => 'https://developer.amazon.com/apps-and-games/services-and-apis', 'icon' => 'icon-amazon', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'vk' => [ 'developers' => 'https://vk.com/dev', 'icon' => 'icon-vk', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'discord' => [ 'developers' => 'https://discordapp.com/developers/docs/topics/oauth2', 'icon' => 'icon-discord', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'twitch' => [ 'developers' => 'https://dev.twitch.tv/docs/authentication', 'icon' => 'icon-twitch', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'spotify' => [ 'developers' => 'https://developer.spotify.com/documentation/general/guides/authorization-guide/', 'icon' => 'icon-spotify', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'yahoo' => [ 'developers' => 'https://developer.yahoo.com/oauth2/guide/flows_authcode/', 'icon' => 'icon-yahoo', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'yandex' => [ 'developers' => 'https://tech.yandex.com/oauth/', 'icon' => 'icon-yandex', 'enabled' => true, + 'form' => false, 'mock' => false, ], 'twitter' => [ 'developers' => 'https://developer.twitter.com/', 'icon' => 'icon-twitter', 'enabled' => false, + 'form' => false, 'mock' => false ], 'paypal' => [ 'developers' => 'https://developer.paypal.com/docs/api/overview/', 'icon' => 'icon-paypal', 'enabled' => true, + 'form' => false, 'mock' => false ], 'bitly' => [ 'developers' => 'https://dev.bitly.com/v4_documentation.html', 'icon' => 'icon-bitly', 'enabled' => true, + 'form' => false, 'mock' => false ], // Keep Last diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index a2b7afe0f..6e7d437fd 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -308,6 +308,29 @@ $utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId') } ); +$utopia->post('/v1/account/sessions/oauth2/callback/:provider/:projectId') + ->desc('OAuth2 Callback') + ->label('error', __DIR__.'/../../views/general/error.phtml') + ->label('scope', 'public') + ->label('origin', '*') + ->label('docs', false) + ->param('projectId', '', function () { return new Text(1024); }, 'Project unique ID.') + ->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.') + ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.') + ->param('state', '', function () { return new Text(2048); }, 'Login state params.', true) + ->action( + function ($projectId, $provider, $code, $state) use ($response) { + $domain = Config::getParam('domain'); + $protocol = Config::getParam('protocol'); + + $response + ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->addHeader('Pragma', 'no-cache') + ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?' + .http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state])); + } + ); + $utopia->get('/v1/account/sessions/oauth2/:provider/redirect') ->desc('OAuth2 Redirect') ->label('error', __DIR__.'/../../views/general/error.phtml') @@ -361,6 +384,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect') if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) { throw new Exception('Invalid redirect URL for failure login', 400); } + $state['failure'] = null; $accessToken = $oauth2->getAccessToken($code); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 54ccd2dce..7cd14e583 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -361,7 +361,7 @@ $utopia->patch('/v1/projects/:projectId/oauth2') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'Provider Name', false) ->param('appId', '', function () { return new Text(256); }, 'Provider app ID.', true) - ->param('secret', '', function () { return new text(256); }, 'Provider secret key.', true) + ->param('secret', '', function () { return new text(512); }, 'Provider secret key.', true) ->action( function ($projectId, $provider, $appId, $secret) use ($request, $response, $consoleDB) { $project = $consoleDB->getDocument($projectId); diff --git a/app/views/console/users/index.phtml b/app/views/console/users/index.phtml index 62cf13a4b..18b8b0537 100644 --- a/app/views/console/users/index.phtml +++ b/app/views/console/users/index.phtml @@ -337,12 +337,13 @@ $providers = $this->getParam('providers', []); $data): if (isset($data['enabled']) && !$data['enabled']) { continue; } if (isset($data['mock']) && $data['mock']) { continue; } + $form = (isset($data['form'])) ? $data['form'] : false; ?>
  • + {{console-project.usersOauth2escape(ucfirst($provider)); ?>Appid}} && + {{console-project.usersOauth2escape(ucfirst($provider)); ?>Secret}}"> - + !{{console-project.usersOauth2escape(ucfirst($provider)); ?>Appid}} || + !{{console-project.usersOauth2escape(ucfirst($provider)); ?>Secret}}"> + - <?php echo ucfirst($provider); ?> Logo + <?php echo $this->escape(ucfirst($provider)); ?> Logo diff --git a/gulpfile.js b/gulpfile.js index a0be3a405..b45d16bf7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -47,6 +47,7 @@ const configApp = { 'public/scripts/views/forms/move-down.js', 'public/scripts/views/forms/move-up.js', 'public/scripts/views/forms/nav.js', + 'public/scripts/views/forms/oauth-apple.js', 'public/scripts/views/forms/password-meter.js', 'public/scripts/views/forms/pell.js', 'public/scripts/views/forms/remove.js', diff --git a/public/dist/scripts/app-all.js b/public/dist/scripts/app-all.js index f7b6ba5a9..3dac3fcbd 100644 --- a/public/dist/scripts/app-all.js +++ b/public/dist/scripts/app-all.js @@ -2568,7 +2568,7 @@ return"< 1s";}).add("markdown",function($value,markdown){return markdown.render( let thresh=1000;if(Math.abs($value)=thresh&&u'+ units[u]+"");}).add("statsTotal",function($value){if(!$value){return 0;} -$value=abbreviate($value,1,false,false);return $value==="0"?"N/A":$value;}).add("isEmpty",function($value){return(!!$value);}).add("isEmptyObject",function($value){return((Object.keys($value).length===0&&$value.constructor===Object)||$value.length===0)}).add("activeDomainsCount",function($value){let result=[];if(Array.isArray($value)){result=$value.filter(function(node){return(node.verification&&node.certificateId);});} +$value=abbreviate($value,0,false,false);return $value==="0"?"N/A":$value;}).add("isEmpty",function($value){return(!!$value);}).add("isEmptyObject",function($value){return((Object.keys($value).length===0&&$value.constructor===Object)||$value.length===0)}).add("activeDomainsCount",function($value){let result=[];if(Array.isArray($value)){result=$value.filter(function(node){return(node.verification&&node.certificateId);});} return result.length;}).add("documentAction",function(container){let collection=container.get('project-collection');let document=container.get('project-document');if(collection&&document&&!document.$id){return'database.createDocument';} return'database.updateDocument';}).add("documentSuccess",function(container){let document=container.get('project-document');if(document&&!document.$id){return',redirect';} return'';}).add("firstElement",function($value){if($value&&$value[0]){return $value[0];} @@ -2640,7 +2640,11 @@ list["filters-"+filter.key]=params[key][i];}}}} return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');} -console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-password-meter",controller:function(element,window){var calc=function(password){var score=0;if(!password)return score;var letters=new window.Object();for(var i=0;i60)return(meter.className="password-meter strong");if(score>30)return(meter.className="password-meter medium");if(score>=0)return(meter.className="password-meter weak");};var meter=window.document.createElement("div");meter.className="password-meter";element.parentNode.insertBefore(meter,element.nextSibling);element.addEventListener("change",callback);element.addEventListener("keypress",callback);element.addEventListener("keyup",callback);element.addEventListener("keydown",callback);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-pell",controller:function(element,window,document,markdown,rtl){var div=document.createElement("div");element.className="pell hide";div.className="input pell";element.parentNode.insertBefore(div,element);element.tabIndex=-1;var turndownService=new TurndownService();turndownService.addRule("underline",{filter:["u"],replacement:function(content){return"__"+content+"__";}});var editor=window.pell.init({element:div,onChange:function onChange(html){alignText();element.value=turndownService.turndown(html);},defaultParagraphSeparator:"p",actions:[{name:"bold",icon:''},{name:"underline",icon:''},{name:"italic",icon:''},{name:"olist",icon:''},{name:"ulist",icon:''},{name:"link",icon:''}]});var clean=function(e){e.stopPropagation();e.preventDefault();var clipboardData=e.clipboardData||window.clipboardData;console.log(clipboardData.getData("Text"));window.pell.exec("insertText",clipboardData.getData("Text"));return true;};var alignText=function(){let paragraphs=editor.content.querySelectorAll('p,li');let last='';for(let paragraph of paragraphs){var content=paragraph.textContent;if(content.trim()===''){content=last.textContent;} if(rtl.isRTL(content)){paragraph.style.direction='rtl';paragraph.style.textAlign='right';} diff --git a/public/dist/scripts/app.js b/public/dist/scripts/app.js index 6643c8f32..15077e480 100644 --- a/public/dist/scripts/app.js +++ b/public/dist/scripts/app.js @@ -284,7 +284,7 @@ return"< 1s";}).add("markdown",function($value,markdown){return markdown.render( let thresh=1000;if(Math.abs($value)=thresh&&u'+ units[u]+"");}).add("statsTotal",function($value){if(!$value){return 0;} -$value=abbreviate($value,1,false,false);return $value==="0"?"N/A":$value;}).add("isEmpty",function($value){return(!!$value);}).add("isEmptyObject",function($value){return((Object.keys($value).length===0&&$value.constructor===Object)||$value.length===0)}).add("activeDomainsCount",function($value){let result=[];if(Array.isArray($value)){result=$value.filter(function(node){return(node.verification&&node.certificateId);});} +$value=abbreviate($value,0,false,false);return $value==="0"?"N/A":$value;}).add("isEmpty",function($value){return(!!$value);}).add("isEmptyObject",function($value){return((Object.keys($value).length===0&&$value.constructor===Object)||$value.length===0)}).add("activeDomainsCount",function($value){let result=[];if(Array.isArray($value)){result=$value.filter(function(node){return(node.verification&&node.certificateId);});} return result.length;}).add("documentAction",function(container){let collection=container.get('project-collection');let document=container.get('project-document');if(collection&&document&&!document.$id){return'database.createDocument';} return'database.updateDocument';}).add("documentSuccess",function(container){let document=container.get('project-document');if(document&&!document.$id){return',redirect';} return'';}).add("firstElement",function($value){if($value&&$value[0]){return $value[0];} @@ -356,7 +356,11 @@ list["filters-"+filter.key]=params[key][i];}}}} return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');} -console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-password-meter",controller:function(element,window){var calc=function(password){var score=0;if(!password)return score;var letters=new window.Object();for(var i=0;i60)return(meter.className="password-meter strong");if(score>30)return(meter.className="password-meter medium");if(score>=0)return(meter.className="password-meter weak");};var meter=window.document.createElement("div");meter.className="password-meter";element.parentNode.insertBefore(meter,element.nextSibling);element.addEventListener("change",callback);element.addEventListener("keypress",callback);element.addEventListener("keyup",callback);element.addEventListener("keydown",callback);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-pell",controller:function(element,window,document,markdown,rtl){var div=document.createElement("div");element.className="pell hide";div.className="input pell";element.parentNode.insertBefore(div,element);element.tabIndex=-1;var turndownService=new TurndownService();turndownService.addRule("underline",{filter:["u"],replacement:function(content){return"__"+content+"__";}});var editor=window.pell.init({element:div,onChange:function onChange(html){alignText();element.value=turndownService.turndown(html);},defaultParagraphSeparator:"p",actions:[{name:"bold",icon:''},{name:"underline",icon:''},{name:"italic",icon:''},{name:"olist",icon:''},{name:"ulist",icon:''},{name:"link",icon:''}]});var clean=function(e){e.stopPropagation();e.preventDefault();var clipboardData=e.clipboardData||window.clipboardData;console.log(clipboardData.getData("Text"));window.pell.exec("insertText",clipboardData.getData("Text"));return true;};var alignText=function(){let paragraphs=editor.content.querySelectorAll('p,li');let last='';for(let paragraph of paragraphs){var content=paragraph.textContent;if(content.trim()===''){content=last.textContent;} if(rtl.isRTL(content)){paragraph.style.direction='rtl';paragraph.style.textAlign='right';} diff --git a/public/scripts/views/forms/oauth-apple.js b/public/scripts/views/forms/oauth-apple.js new file mode 100644 index 000000000..358e9aae8 --- /dev/null +++ b/public/scripts/views/forms/oauth-apple.js @@ -0,0 +1,94 @@ +(function(window) { + "use strict"; + + window.ls.container.get("view").add({ + selector: "data-forms-oauth-apple", + controller: function(element) { + let container = document.createElement("div"); + let row = document.createElement("div"); + let col1 = document.createElement("div"); + let col2 = document.createElement("div"); + let keyID = document.createElement("input"); + let keyLabel = document.createElement("label"); + let teamID = document.createElement("input"); + let teamLabel = document.createElement("label"); + let p8 = document.createElement("textarea"); + let p8Label = document.createElement("label"); + + keyLabel.textContent = 'Key ID'; + teamLabel.textContent = 'Team ID'; + p8Label.textContent = 'P8 File'; + + row.classList.add('row'); + row.classList.add('thin'); + container.appendChild(row); + container.appendChild(p8Label); + container.appendChild(p8); + + row.appendChild(col1); + row.appendChild(col2); + + col1.classList.add('col'); + col1.classList.add('span-6'); + col1.appendChild(keyLabel); + col1.appendChild(keyID); + + col2.classList.add('col'); + col2.classList.add('span-6'); + col2.appendChild(teamLabel); + col2.appendChild(teamID); + + keyID.type = 'text'; + keyID.placeholder = 'SHAB13ROFN'; + teamID.type = 'text'; + teamID.placeholder = 'ELA2CD3AED'; + p8.accept = '.p8'; + p8.classList.add('margin-bottom-no'); + + element.parentNode.insertBefore(container, element.nextSibling); + + element.addEventListener('change', sync); + keyID.addEventListener('change', update); + teamID.addEventListener('change', update); + p8.addEventListener('change', update); + + function update() { + let json = {}; + + json.keyID = keyID.value; + json.teamID = teamID.value; + json.p8 = p8.value; + + element.value = JSON.stringify(json); + } + + function sync() { + console.log('sync'); + if(!element.value) { + return; + } + + let json = {}; + + try { + json = JSON.parse(element.value); + } catch (error) { + console.error('Failed to parse secret key'); + } + + teamID.value = json.teamID || ''; + keyID.value = json.keyID || ''; + p8.value = json.p8 || ''; + } + + // function syncB() { + // picker.value = element.value; + // } + + // element.parentNode.insertBefore(preview, element); + + // update(); + sync(); + } + }); +})(window); diff --git a/src/Appwrite/Auth/OAuth2/Apple.php b/src/Appwrite/Auth/OAuth2/Apple.php index 22ed29561..411f8baf5 100644 --- a/src/Appwrite/Auth/OAuth2/Apple.php +++ b/src/Appwrite/Auth/OAuth2/Apple.php @@ -3,6 +3,7 @@ namespace Appwrite\Auth\OAuth2; use Appwrite\Auth\OAuth2; +use Exception; // Reference Material // https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple @@ -22,6 +23,11 @@ class Apple extends OAuth2 "email" ]; + /** + * @var array + */ + protected $claims = []; + /** * @return string */ @@ -58,15 +64,18 @@ class Apple extends OAuth2 'https://appleid.apple.com/auth/token', $headers, http_build_query([ + 'grant_type' => 'authorization_code', 'code' => $code, 'client_id' => $this->appID, - 'client_secret' => $this->appSecret, + 'client_secret' => $this->getAppSecret(), 'redirect_uri' => $this->callback, - 'grant_type' => 'authorization_code' ]) ); - $accessToken = json_decode($accessToken, true); + $accessToken = json_decode($accessToken, true); + + $this->claims = (isset($accessToken['id_token'])) ? explode('.', $accessToken['id_token']) : [0 => '', 1 => '']; + $this->claims = (isset($this->claims[1])) ? json_decode(base64_decode($this->claims[1]), true) : []; if (isset($accessToken['access_token'])) { return $accessToken['access_token']; @@ -82,10 +91,8 @@ class Apple extends OAuth2 */ public function getUserID(string $accessToken): string { - $user = $this->getUser($accessToken); - - if (isset($user['account_id'])) { - return $user['account_id']; + if (isset($this->claims['sub']) && !empty($this->claims['sub'])) { + return $this->claims['sub']; } return ''; @@ -98,10 +105,11 @@ class Apple extends OAuth2 */ public function getUserEmail(string $accessToken): string { - $user = $this->getUser($accessToken); - - if (isset($user['email'])) { - return $user['email']; + if (isset($this->claims['email']) && + !empty($this->claims['email']) && + isset($this->claims['email_verified']) && + $this->claims['email_verified'] === 'true') { + return $this->claims['email']; } return ''; @@ -114,57 +122,111 @@ class Apple extends OAuth2 */ public function getUserName(string $accessToken): string { - $user = $this->getUser($accessToken); - - if (isset($user['name'])) { - return $user['name']['display_name']; + if (isset($this->claims['email']) && + !empty($this->claims['email']) && + isset($this->claims['email_verified']) && + $this->claims['email_verified'] === 'true') { + return $this->claims['email']; } return ''; } - /** - * @param string $accessToken - * - * @return array - */ - protected function getUser(string $accessToken): array + protected function getAppSecret():string { - if (empty($this->user)) { - $headers[] = 'Authorization: Bearer '. urlencode($accessToken); - $user = $this->request('POST', '', $headers); - $this->user = json_decode($user, true); + try { + $secret = json_decode($this->appSecret, true); + } catch (\Throwable $th) { + throw new Exception('Invalid secret'); } - return $this->user; - } + $keyfile = (isset($secret['p8'])) ? $secret['p8'] : ''; // Your p8 Key file + $keyID = (isset($secret['keyID'])) ? $secret['keyID'] : ''; // Your Key ID + $teamID = (isset($secret['teamID'])) ? $secret['teamID'] : ''; // Your Team ID (see Developer Portal) + $bundleID = $this->appID; // Your Bundle ID - protected function getToken($p8) - { - $keyfile = 'AuthKey_AABBCC1234.p8'; # <- Your AuthKey file - $keyid = '4LFF7TZ6Q5'; # <- Your Key ID - $teamid = 'YJHMCSNREU'; # <- Your Team ID (see Developer Portal) - $bundleid = 'test2.appwrite.io'; # <- Your Bundle ID - $url = 'https://api.development.push.apple.com'; # <- development url, or use http://api.push.apple.com for production environment - $token = 'e2c48ed32ef9b018........'; # <- Device Token + $headers = [ + 'alg' => 'ES256', + 'kid' => $keyID, + ]; - function base64($data) { - return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '='); - } + $claims = [ + 'iss' => $teamID, + 'iat' => time(), + 'exp' => time() + 86400*180, + 'aud' => 'https://appleid.apple.com', + 'sub' => $bundleID, + ]; - $message = '{"aps":{"alert":"Hi there!","sound":"default"}}'; + $pkey = openssl_pkey_get_private($keyfile); - $key = openssl_pkey_get_private('file://'.$keyfile); - - $header = ['alg'=>'ES256', 'kid'=>$keyid]; - $claims = ['iss'=>$teamid, 'iat'=>time()]; - - $header_encoded = base64($header); - $claims_encoded = base64($claims); + $payload = $this->encode(json_encode($headers)).'.'.$this->encode(json_encode($claims)); $signature = ''; - openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256'); - $jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature); + $success = openssl_sign($payload, $signature, $pkey, OPENSSL_ALGO_SHA256); + + if (!$success) return ''; + + return $payload.'.'.$this->encode($this->fromDER($signature, 64)); + } + + /** + * @param string $data + */ + protected function encode($data) + { + return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data)); + } + + /** + * @param string $data + */ + protected function retrievePositiveInteger(string $data): string + { + while ('00' === mb_substr($data, 0, 2, '8bit') && mb_substr($data, 2, 2, '8bit') > '7f') { + $data = mb_substr($data, 2, null, '8bit'); + } + + return $data; + } + + /** + * @param string $der + * @param int $partLength + */ + protected function fromDER(string $der, int $partLength):string + { + $hex = \unpack('H*', $der)[1]; + + if ('30' !== \mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE + throw new \RuntimeException(); + } + + if ('81' === \mb_substr($hex, 2, 2, '8bit')) { // LENGTH > 128 + $hex = \mb_substr($hex, 6, null, '8bit'); + } + else { + $hex = \mb_substr($hex, 4, null, '8bit'); + } + if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER + throw new \RuntimeException(); + } + + $Rl = \hexdec(\mb_substr($hex, 2, 2, '8bit')); + $R = $this->retrievePositiveInteger(\mb_substr($hex, 4, $Rl * 2, '8bit')); + $R = \str_pad($R, $partLength, '0', STR_PAD_LEFT); + + $hex = \mb_substr($hex, 4 + $Rl * 2, null, '8bit'); + + if ('02' !== \mb_substr($hex, 0, 2, '8bit')) { // INTEGER + throw new \RuntimeException(); + } + + $Sl = \hexdec(\mb_substr($hex, 2, 2, '8bit')); + $S = $this->retrievePositiveInteger(\mb_substr($hex, 4, $Sl * 2, '8bit')); + $S = \str_pad($S, $partLength, '0', STR_PAD_LEFT); + + return \pack('H*', $R.$S); } } From 14e6c61ed99f1b91c7331ead9a270137cbce66f9 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 29 May 2020 15:46:26 +0300 Subject: [PATCH 3/4] Updated docs --- CHANGES.md | 1 + app/config/providers.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 39a104637..32c0765f6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## Features +- New OAuth adapter for sign-in with Apple - New route in Locale API to fetch list of languages ## Bug Fixes diff --git a/app/config/providers.php b/app/config/providers.php index 1a6b067ec..0d788de3f 100644 --- a/app/config/providers.php +++ b/app/config/providers.php @@ -87,7 +87,7 @@ return [ 'developers' => 'https://developer.apple.com/', 'icon' => 'icon-apple', 'enabled' => true, - 'form' => 'apple.phtml', + 'form' => 'apple.phtml', // Perperation for adding ability to customized OAuth UI forms, currently handled hardcoded. 'mock' => false, ], 'amazon' => [ From 116521f4a7ef6e76a688f9ff7b005d83c89b1fbf Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 29 May 2020 19:43:59 +0300 Subject: [PATCH 4/4] Added beta tag --- app/config/providers.php | 25 +++++++++++++++++++++++++ app/views/console/users/index.phtml | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/config/providers.php b/app/config/providers.php index 0d788de3f..7eb5113d9 100644 --- a/app/config/providers.php +++ b/app/config/providers.php @@ -6,6 +6,7 @@ return [ 'icon' => 'icon-bitbucket', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'facebook' => [ @@ -13,6 +14,7 @@ return [ 'icon' => 'icon-facebook', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'github' => [ @@ -20,6 +22,7 @@ return [ 'icon' => 'icon-github-circled', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'gitlab' => [ @@ -27,6 +30,7 @@ return [ 'icon' => 'icon-gitlab', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'google' => [ @@ -34,12 +38,14 @@ return [ 'icon' => 'icon-google', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], // 'instagram' => [ // 'developers' => 'https://www.instagram.com/developer/', // 'icon' => 'icon-instagram', // 'enabled' => false, + // 'beta' => false, // 'mock' => false, // ], 'microsoft' => [ @@ -47,12 +53,14 @@ return [ 'icon' => 'icon-windows', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], // 'twitter' => [ // 'developers' => 'https://developer.twitter.com/', // 'icon' => 'icon-twitter', // 'enabled' => false, + // 'beta' => false, // 'mock' => false, // ], 'linkedin' => [ @@ -60,6 +68,7 @@ return [ 'icon' => 'icon-linkedin', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'slack' => [ @@ -67,6 +76,7 @@ return [ 'icon' => 'icon-slack', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'dropbox' => [ @@ -74,6 +84,7 @@ return [ 'icon' => 'icon-dropbox', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'salesforce' => [ @@ -81,6 +92,7 @@ return [ 'icon' => 'icon-salesforce', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'apple' => [ @@ -88,6 +100,7 @@ return [ 'icon' => 'icon-apple', 'enabled' => true, 'form' => 'apple.phtml', // Perperation for adding ability to customized OAuth UI forms, currently handled hardcoded. + 'beta' => true, 'mock' => false, ], 'amazon' => [ @@ -95,6 +108,7 @@ return [ 'icon' => 'icon-amazon', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'vk' => [ @@ -102,6 +116,7 @@ return [ 'icon' => 'icon-vk', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'discord' => [ @@ -109,6 +124,7 @@ return [ 'icon' => 'icon-discord', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'twitch' => [ @@ -116,6 +132,7 @@ return [ 'icon' => 'icon-twitch', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'spotify' => [ @@ -123,6 +140,7 @@ return [ 'icon' => 'icon-spotify', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'yahoo' => [ @@ -130,6 +148,7 @@ return [ 'icon' => 'icon-yahoo', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'yandex' => [ @@ -137,6 +156,7 @@ return [ 'icon' => 'icon-yandex', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false, ], 'twitter' => [ @@ -144,6 +164,7 @@ return [ 'icon' => 'icon-twitter', 'enabled' => false, 'form' => false, + 'beta' => false, 'mock' => false ], 'paypal' => [ @@ -151,6 +172,7 @@ return [ 'icon' => 'icon-paypal', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false ], 'bitly' => [ @@ -158,6 +180,7 @@ return [ 'icon' => 'icon-bitly', 'enabled' => true, 'form' => false, + 'beta' => false, 'mock' => false ], // Keep Last @@ -165,6 +188,8 @@ return [ 'developers' => 'https://appwrite.io', 'icon' => 'icon-appwrite', 'enabled' => true, + 'form' => false, + 'beta' => false, 'mock' => true, ] ]; diff --git a/app/views/console/users/index.phtml b/app/views/console/users/index.phtml index 18b8b0537..b7fbdf053 100644 --- a/app/views/console/users/index.phtml +++ b/app/views/console/users/index.phtml @@ -338,6 +338,7 @@ $providers = $this->getParam('providers', []); if (isset($data['enabled']) && !$data['enabled']) { continue; } if (isset($data['mock']) && $data['mock']) { continue; } $form = (isset($data['form'])) ? $data['form'] : false; + $beta = (isset($data['beta'])) ? $data['beta'] : false; ?>