From 517ac8dcf052ebafc13c5a6d5ad15157036ac92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Fri, 6 Sep 2013 22:57:09 -0400 Subject: [PATCH] Add the possibility to link a Google Account or a Mozilla Account to Miniflux --- assets/js/app.js | 31 ++++++++ assets/js/persona.js | 8 ++ index.php | 127 +++++++++++++++++++++++++++++- locales/fr_FR/translations.php | 11 +++ model.php | 40 ++++++++-- schema.php | 7 ++ templates/app_header.php | 2 +- templates/config.php | 21 ++++- templates/login.php | 14 +++- vendor/PicoTools/AuthProvider.php | 87 ++++++++++++++++++++ 10 files changed, 335 insertions(+), 13 deletions(-) create mode 100644 assets/js/persona.js create mode 100644 vendor/PicoTools/AuthProvider.php diff --git a/assets/js/app.js b/assets/js/app.js index 773a122..b262194 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -464,6 +464,29 @@ } + function mozilla_auth(action) + { + navigator.id.watch({ + onlogin: function(assertion) { + + var xhr = new XMLHttpRequest(); + xhr.open("POST", "?action=" + action, true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.setRequestHeader("Connection", "close"); + + xhr.onload = function () { + window.location.href = this.responseText; + }; + + xhr.send("token=" + assertion); + }, + onlogout: function() {} + }); + + navigator.id.request(); + } + + document.onclick = function(e) { var action = e.target.getAttribute("data-action"); @@ -502,6 +525,14 @@ e.preventDefault(); download_item(); break; + case 'mozilla-login': + e.preventDefault(); + mozilla_auth("mozilla-auth"); + break; + case 'mozilla-link': + e.preventDefault(); + mozilla_auth("mozilla-link"); + break; } } }; diff --git a/assets/js/persona.js b/assets/js/persona.js new file mode 100644 index 0000000..04cf67f --- /dev/null +++ b/assets/js/persona.js @@ -0,0 +1,8 @@ +/** + * Uncompressed source can be found at https://login.persona.org/include.orig.js + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +(function(){var a,b=function(){function f(a){return Array.isArray?Array.isArray(a):a.constructor.toString().indexOf("Array")!=-1}function e(a,b,d){var e=c[b][d];for(var f=0;f1)throw"scope may not contain double colons: '::'"}var k=function(){var a="",b="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";for(var c=0;c<5;c++)a+=b.charAt(Math.floor(Math.random()*b.length));return a}(),l={},m={},n={},o=!1,p=[],q=function(a,b,c){var d=!1,e=!1;return{origin:b,invoke:function(b,d){if(!n[a])throw"attempting to invoke a callback of a nonexistent transaction: "+a;var e=!1;for(var f=0;f0)for(var k=0;k=0;e--)try{if(c[e].location.href.indexOf(d)===0&&c[e].name===b)return c[e]}catch(f){}return}function i(a){/^https?:\/\//.test(a)||(a=window.location.href);var b=/^(https?:\/\/[\-_a-zA-Z\.0-9:]+)/.exec(a);return b?b[1]:a}function h(){return window.JSON&&window.JSON.stringify&&window.JSON.parse&&window.postMessage}function g(){try{var a=navigator.userAgent;return a.indexOf("Fennec/")!=-1||a.indexOf("Firefox/")!=-1&&a.indexOf("Android")!=-1}catch(b){}return!1}function f(){var a=-1;if(navigator.appName==="Microsoft Internet Explorer"){var b=navigator.userAgent,c=new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");c.exec(b)!=null&&(a=parseFloat(RegExp.$1))}return a>=8}function e(a,b,c){a.detachEvent?a.detachEvent("on"+b,c):a.removeEventListener&&a.removeEventListener(b,c,!1)}function d(a,b,c){a.attachEvent?a.attachEvent("on"+b,c):a.addEventListener&&a.addEventListener(b,c,!1)}var b="__winchan_relay_frame",c="die",k=f();return h()?{open:function(f,h){function s(a){if(a.origin===m)try{var b=JSON.parse(a.data);b.a==="ready"?n.postMessage(q,m):b.a==="error"?(r(),h&&(h(b.d),h=null)):b.a==="response"&&(r(),h&&(h(null,b.d),h=null))}catch(c){}}function r(){l&&document.body.removeChild(l),l=a,p&&(p=clearInterval(p)),e(window,"message",s),e(window,"unload",r);if(o)try{o.close()}catch(b){n.postMessage(c,m)}o=n=a}if(!h)throw"missing required callback argument";var j;f.url||(j="missing required 'url' parameter"),f.relay_url||(j="missing required 'relay_url' parameter"),j&&setTimeout(function(){h(j)},0),f.window_name||(f.window_name=null);if(!f.window_features||g())f.window_features=a;var l,m=i(f.url);if(m!==i(f.relay_url))return setTimeout(function(){h("invalid arguments: origin of url and relay_url must match")},0);var n;k&&(l=document.createElement("iframe"),l.setAttribute("src",f.relay_url),l.style.display="none",l.setAttribute("name",b),document.body.appendChild(l),n=l.contentWindow);var o=window.open(f.url,f.window_name,f.window_features);n||(n=o);var p=setInterval(function(){o&&o.closed&&(r(),h&&(h("unknown closed window"),h=null))},500),q=JSON.stringify({a:"request",d:f.params});d(window,"unload",r),d(window,"message",s);return{close:r,focus:function(){if(o)try{o.focus()}catch(a){}}}},onOpen:function(b){function l(a){if(a.data===c)try{window.close()}catch(b){}}function i(c){var d;try{d=JSON.parse(c.data)}catch(g){}!!d&&d.a==="request"&&(e(window,"message",i),f=c.origin,b&&setTimeout(function(){b(f,d.d,function(c){b=a,h({a:"response",d:c})})},0))}function h(a){a=JSON.stringify(a),k?g.doPost(a,f):g.postMessage(a,f)}var f="*",g=k?j():window.opener;if(!g)throw"can't find relay frame";d(k?g:window,"message",i),d(k?g:window,"message",l);try{h({a:"ready"})}catch(m){d(g,"load",function(a){h({a:"ready"})})}var n=function(){try{e(k?g:window,"message",l)}catch(c){}b&&h({a:"error",d:"client closed window"}),b=a;try{window.close()}catch(d){}};d(window,"unload",n);return{detach:function(){e(window,"unload",n)}}}}:{open:function(a,b,c,d){setTimeout(function(){d("unsupported browser")},0)},onOpen:function(a){setTimeout(function(){a("unsupported browser")},0)}}}();var c=function(){function l(){return c}function k(){c=g()||h()||i()||j();return!c}function j(){if(!(window.JSON&&window.JSON.stringify&&window.JSON.parse))return"JSON_NOT_SUPPORTED"}function i(){if(!a.postMessage)return"POSTMESSAGE_NOT_SUPPORTED"}function h(){try{var b="localStorage"in a&&a.localStorage!==null;if(b)a.localStorage.setItem("test","true"),a.localStorage.removeItem("test");else return"LOCALSTORAGE_NOT_SUPPORTED"}catch(c){return"LOCALSTORAGE_DISABLED"}}function g(){return f()}function f(){var a=e(),b=a>-1&&a<8;if(b)return"BAD_IE_VERSION"}function e(){var a=-1;if(b.appName=="Microsoft Internet Explorer"){var c=b.userAgent,d=new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");d.exec(c)!=null&&(a=parseFloat(RegExp.$1))}return a}function d(c,d){b=c,a=d}var a=window,b=navigator,c;return{setTestEnv:d,isSupported:k,getNoSupportReason:l}}();navigator.id||(navigator.id={},navigator.mozId?navigator.id=navigator.mozId:navigator.id={});if(!navigator.id.request||navigator.id._shimmed){var d="https://login.persona.org",e=navigator.userAgent,f=e.indexOf("Fennec/")!=-1||e.indexOf("Firefox/")!=-1&&e.indexOf("Android")!=-1,g=f?a:"menubar=0,location=1,resizable=1,scrollbars=1,status=0,width=700,height=375",h,i={login:null,logout:null,match:null,ready:null},j,k=a;function l(b){b!==!0;if(k===a)k=b;else if(k!=b)throw new Error("you cannot combine the navigator.id.watch() API with navigator.id.getVerifiedEmail() or navigator.id.get()this site should instead use navigator.id.request() and navigator.id.watch()")}var m,n=!1,o=c.isSupported();function p(a){document.addEventListener?document.addEventListener("DOMContentLoaded",function b(){document.removeEventListener("DOMContentLoaded",b),a()},!1):document.attachEvent&&document.readyState&&document.attachEvent("onreadystatechange",function c(){var b=document.readyState;if(b==="loaded"||b==="complete"||b==="interactive")document.detachEvent("onreadystatechange",c),a()})}function q(){if(!!o){var c=window.document;if(!c.body){n||(p(q),n=!0);return}try{if(!m){var e=c.createElement("iframe");e.style.display="none",c.body.appendChild(e),e.src=d+"/communication_iframe",m=b.build({window:e.contentWindow,origin:d,scope:"mozid_ni",onReady:function(){m.call({method:"loaded",success:function(){i.ready&&i.ready()},error:function(){}})}}),m.bind("logout",function(a,b){i.logout&&i.logout()}),m.bind("login",function(a,b){i.login&&i.login(b)}),m.bind("match",function(a,b){i.match&&i.match()}),r(j)&&m.notify({method:"loggedInUser",params:j})}}catch(f){m=a}}}function r(a){return typeof a!="undefined"}function s(a){try{console.warn(a)}catch(b){}}function t(a,b){if(r(a[b])){s(b+" has been deprecated");return!0}}function u(a,b,c){if(r(a[b])&&r(a[c]))throw new Error("you cannot supply *both* "+b+" and "+c);t(a,b)&&(a[c]=a[b],delete a[b])}function v(a){if(typeof a=="object"){if(a.onlogin&&typeof a.onlogin!="function"||a.onlogout&&typeof a.onlogout!="function"||a.onmatch&&typeof a.onmatch!="function"||a.onready&&typeof a.onready!="function")throw new Error("non-function where function expected in parameters to navigator.id.watch()");if(!a.onlogin)throw new Error("'onlogin' is a required argument to navigator.id.watch()");if(!a.onlogout)throw new Error("'onlogout' is a required argument to navigator.id.watch()");i.login=a.onlogin||null,i.logout=a.onlogout||null,i.match=a.onmatch||null,i.ready=a.onready||null,u(a,"loggedInEmail","loggedInUser"),j=a.loggedInUser,q()}}var w;function x(){var a=w;a==="request"&&(i.ready?a="watch_with_onready":a="watch_without_onready");return a}function y(b){t(b,"requiredEmail"),u(b,"tosURL","termsOfService"),u(b,"privacyURL","privacyPolicy"),b.termsOfService&&!b.privacyPolicy&&s("termsOfService ignored unless privacyPolicy also defined"),b.privacyPolicy&&!b.termsOfService&&s("privacyPolicy ignored unless termsOfService also defined"),b.rp_api=x(),w=null,b.start_time=(new Date).getTime();if(h)try{h.focus()}catch(e){}else{if(!c.isSupported()){var f=c.getNoSupportReason(),j="unsupported_dialog";f==="LOCALSTORAGE_DISABLED"&&(j="cookies_disabled"),h=window.open(d+"/"+j,null,g);return}m&&m.notify({method:"dialog_running"}),h=WinChan.open({url:d+"/sign_in",relay_url:d+"/relay",window_features:g,window_name:"__persona_dialog",params:{method:"get",params:b}},function(c,d){if(m){!c&&d&&d.email&&m.notify({method:"loggedInUser",params:d.email});var e=!(c||d&&d.assertion);m.notify({method:"dialog_complete",params:e})}h=a;if(!c&&d&&d.assertion)try{i.login&&i.login(d.assertion)}catch(f){console.log(f);throw f}if(c==="client closed window"||!d)b&&b.oncancel&&b.oncancel(),delete b.oncancel})}}navigator.id={request:function(a){if(this!=navigator.id)throw new Error("all navigator.id calls must be made on the navigator.id object");if(!i.login)throw new Error("navigator.id.watch must be called before navigator.id.request");a=a||{},l(!1),w="request",a.returnTo||(a.returnTo=document.location.pathname);return y(a)},watch:function(a){if(this!=navigator.id)throw new Error("all navigator.id calls must be made on the navigator.id object");l(!1),v(a)},logout:function(a){if(this!=navigator.id)throw new Error("all navigator.id calls must be made on the navigator.id object");q(),m&&m.notify({method:"logout"}),typeof a=="function"&&(s("navigator.id.logout callback argument has been deprecated."),setTimeout(a,0))},get:function(b,c){var d={};c=c||{},d.privacyPolicy=c.privacyPolicy||a,d.termsOfService=c.termsOfService||a,d.privacyURL=c.privacyURL||a,d.tosURL=c.tosURL||a,d.siteName=c.siteName||a,d.siteLogo=c.siteLogo||a,d.backgroundColor=c.backgroundColor||a,w=w||"get";t(c,"silent")?b&&setTimeout(function(){b(null)},0):(l(!0),v({onlogin:function(a){b&&(b(a),b=null)},onlogout:function(){}}),d.oncancel=function(){b&&(b(null),b=null),i.login=i.logout=i.match=i.ready=null},y(d))},getVerifiedEmail:function(a){s("navigator.id.getVerifiedEmail has been deprecated"),l(!0),w="getVerifiedEmail",navigator.id.get(a)},_shimmed:!0}}})() \ No newline at end of file diff --git a/index.php b/index.php index 536839e..826c5c2 100644 --- a/index.php +++ b/index.php @@ -23,8 +23,9 @@ Session\open(dirname($_SERVER['PHP_SELF'])); // Called before each action Router\before(function($action) { - if ($action !== 'login' && ! isset($_SESSION['user'])) { + $ignore_actions = array('login', 'google-auth', 'google-redirect-auth', 'mozilla-auth'); + if (! isset($_SESSION['user']) && ! in_array($action, $ignore_actions)) { Response\redirect('?action=login'); } @@ -33,10 +34,13 @@ Router\before(function($action) { if ($language !== 'en_US') PicoTools\Translator\load($language); // HTTP secure headers + $frame_src = \PicoFeed\Filter::$iframe_whitelist; + $frame_src[] = 'https://login.persona.org'; + Response\csp(array( 'media-src' => '*', 'img-src' => '*', - 'frame-src' => \PicoFeed\Filter::$iframe_whitelist + 'frame-src' => $frame_src )); Response\xframe(); @@ -56,9 +60,11 @@ Router\get_action('logout', function() { // Display form login Router\get_action('login', function() { - if (isset($_SESSION['user'])) Response\redirect('index.php'); + if (isset($_SESSION['user'])) Response\redirect('?action=unread'); Response\html(Template\load('login', array( + 'google_auth_enable' => Model\get_config_value('auth_google_token') !== '', + 'mozilla_auth_enable' => Model\get_config_value('auth_mozilla_token') !== '', 'errors' => array(), 'values' => array() ))); @@ -74,6 +80,8 @@ Router\post_action('login', function() { if ($valid) Response\redirect('?action=unread'); Response\html(Template\load('login', array( + 'google_auth_enable' => Model\get_config_value('auth_google_token') !== '', + 'mozilla_auth_enable' => Model\get_config_value('auth_mozilla_token') !== '', 'errors' => $errors, 'values' => $values ))); @@ -658,6 +666,117 @@ Router\post_action('config', function() { }); +// Link to a Google Account (redirect) +Router\get_action('google-redirect-link', function() { + + require 'vendor/PicoTools/AuthProvider.php'; + Response\Redirect(AuthProvider\google_get_url(Helper\get_current_base_url(), '?action=google-link')); +}); + + +// Link to a Google Account (association) +Router\get_action('google-link', function() { + + require 'vendor/PicoTools/AuthProvider.php'; + + list($valid, $token) = AuthProvider\google_validate(); + + if ($valid) { + Model\save_auth_token('google', $token); + Session\flash(t('Your Google Account is linked to Miniflux.')); + } + else { + Session\flash_error(t('Unable to link Miniflux to your Google Account.')); + } + + Response\redirect('?action=config'); +}); + + +// Authenticate with a Google Account (redirect) +Router\get_action('google-redirect-auth', function() { + + require 'vendor/PicoTools/AuthProvider.php'; + Response\Redirect(AuthProvider\google_get_url(Helper\get_current_base_url(), '?action=google-auth')); +}); + + +// Authenticate with a Google Account (callback url) +Router\get_action('google-auth', function() { + + require 'vendor/PicoTools/AuthProvider.php'; + + list($valid, $token) = AuthProvider\google_validate(); + + if ($valid && $token === Model\get_config_value('auth_google_token')) { + + $_SESSION['user'] = array( + 'username' => Model\get_config_value('username'), + 'language' => Model\get_config_value('language'), + ); + + Response\redirect('?action=unread'); + } + else { + + Response\html(Template\load('login', array( + 'google_auth_enable' => Model\get_config_value('auth_google_token') !== '', + 'mozilla_auth_enable' => Model\get_config_value('auth_mozilla_token') !== '', + 'errors' => array('login' => t('Unable to authenticate with Google')), + 'values' => array() + ))); + } +}); + + +// Authenticate with a Mozilla Persona (ajax check) +Router\post_action('mozilla-auth', function() { + + require 'vendor/PicoTools/AuthProvider.php'; + + list($valid, $token) = AuthProvider\mozilla_validate(Request\value('token')); + + if ($valid && $token === Model\get_config_value('auth_mozilla_token')) { + + $_SESSION['user'] = array( + 'username' => Model\get_config_value('username'), + 'language' => Model\get_config_value('language'), + ); + + Response\text('?action=unread'); + } + else { + Response\text("?action=login"); + } +}); + + +// Link Miniflux to a Mozilla Account (ajax check) +Router\post_action('mozilla-link', function() { + + require 'vendor/PicoTools/AuthProvider.php'; + + list($valid, $token) = AuthProvider\mozilla_validate(Request\value('token')); + + if ($valid) { + Model\save_auth_token('mozilla', $token); + Session\flash(t('Your Mozilla Persona Account is linked to Miniflux.')); + } + else { + Session\flash_error(t('Unable to link Miniflux to your Mozilla Persona Account.')); + } + + Response\text("?action=config"); +}); + + +// Remove account link +Router\get_action('unlink-account-provider', function() { + Model\remove_auth_token(Request\param('type')); + Response\redirect('?action=config'); +}); + + // Display unread items Router\notfound(function() { @@ -665,7 +784,7 @@ Router\notfound(function() { $offset = Request\int_param('offset', 0); $items = Model\get_items('unread', $offset, Model\get_config_value('items_per_page')); - $nb_items = Model\count_items('unread');; + $nb_items = Model\count_items('unread'); if ($nb_items === 0) Response\redirect('?action=feeds¬hing_to_read=1'); diff --git a/locales/fr_FR/translations.php b/locales/fr_FR/translations.php index 8ec548a..3984da6 100644 --- a/locales/fr_FR/translations.php +++ b/locales/fr_FR/translations.php @@ -1,6 +1,17 @@ 'Votre compte Google est relié à Miniflux', + 'Link Miniflux to my Google account' => 'Lier Miniflux à mon compte Google', + 'Your Mozilla Persona Account is linked to Miniflux' => 'Votre compte Mozilla Persona est relié à Miniflux', + 'Link Miniflux to my Mozilla Persona account' => 'Lier Miniflux à mon compte Mozilla Persona', + 'Your Google Account is linked to Miniflux.' => 'Votre compte Google est relié à Miniflux.', + 'Unable to link Miniflux to your Google Account.' => 'Impossible de lier Miniflux à votre compte Google', + 'Unable to authenticate with Google' => 'Impossible de s\'authentifier avec Google', + 'Your Mozilla Persona Account is linked to Miniflux.' => 'Votre compte Mozilla Persona est lié avec Miniflux.', + 'Unable to link Miniflux to your Mozilla Persona Account.' => 'Impossible de lier Miniflux avec votre compte Mozilla Persona.', + 'Login with my Google Account' => 'Se connecter avec mon compte Google', + 'Login with my Mozilla Persona Account' => 'Se connecter avec mon compte Mozilla Persona', 'Bookmarklet:' => 'Bookmarklet :', 'Subscribe with Miniflux' => 'S\'abonner avec Miniflux', 'Drag and drop this link to your bookmarks' => 'Glisser-déposer ce lien dans vos favoris', diff --git a/model.php b/model.php index cb345f7..776ea7c 100644 --- a/model.php +++ b/model.php @@ -24,7 +24,7 @@ use PicoFeed\Reader; use PicoFeed\Export; -const DB_VERSION = 15; +const DB_VERSION = 16; const HTTP_USERAGENT = 'Miniflux - http://miniflux.net'; const HTTP_FAKE_USERAGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36'; const LIMIT_ALL = -1; @@ -126,6 +126,28 @@ function new_tokens() } +function save_auth_token($type, $value) +{ + return \PicoTools\singleton('db') + ->table('config') + ->update(array( + 'auth_'.$type.'_token' => $value + )); +} + + +function remove_auth_token($type) +{ + \PicoTools\singleton('db') + ->table('config') + ->update(array( + 'auth_'.$type.'_token' => '' + )); + + $_SESSION['config'] = get_config(); +} + + function export_feeds() { $opml = new Export(get_feeds()); @@ -885,7 +907,18 @@ function get_config() { return \PicoTools\singleton('db') ->table('config') - ->columns('username', 'language', 'autoflush', 'nocontent', 'items_per_page', 'theme', 'api_token', 'feed_token') + ->columns( + 'username', + 'language', + 'autoflush', + 'nocontent', + 'items_per_page', + 'theme', + 'api_token', + 'feed_token', + 'auth_google_token', + 'auth_mozilla_token' + ) ->findOne(); } @@ -976,11 +1009,8 @@ function save_config(array $values) { // Update the password if needed if (! empty($values['password'])) { - $values['password'] = \password_hash($values['password'], PASSWORD_BCRYPT); - } else { - unset($values['password']); } diff --git a/schema.php b/schema.php index 4e0de33..c1fcaf5 100644 --- a/schema.php +++ b/schema.php @@ -3,6 +3,13 @@ namespace Schema; +function version_16($pdo) +{ + $pdo->exec('ALTER TABLE config ADD COLUMN auth_google_token TEXT DEFAULT ""'); + $pdo->exec('ALTER TABLE config ADD COLUMN auth_mozilla_token TEXT DEFAULT ""'); +} + + function version_15($pdo) { $pdo->exec('ALTER TABLE feeds ADD COLUMN download_content INTEGER DEFAULT 0'); diff --git a/templates/app_header.php b/templates/app_header.php index f708821..b822302 100644 --- a/templates/app_header.php +++ b/templates/app_header.php @@ -11,7 +11,7 @@ <?= isset($title) ? Helper\escape($title) : 'miniflux' ?> - +
diff --git a/templates/config.php b/templates/config.php index e50d899..0e71a05 100644 --- a/templates/config.php +++ b/templates/config.php @@ -27,6 +27,23 @@
+
    +
  • + + , + + + +
  • +
  • + + , + + + +
  • +
+
@@ -70,4 +87,6 @@
  • - \ No newline at end of file + + + \ No newline at end of file diff --git a/templates/login.php b/templates/login.php index 760738f..6635e16 100644 --- a/templates/login.php +++ b/templates/login.php @@ -11,6 +11,10 @@ miniflux + + + +
    @@ -20,9 +24,7 @@
    -

    -
    @@ -33,6 +35,14 @@ + +


    + + + +


    + +
    diff --git a/vendor/PicoTools/AuthProvider.php b/vendor/PicoTools/AuthProvider.php new file mode 100644 index 0000000..18fa7e5 --- /dev/null +++ b/vendor/PicoTools/AuthProvider.php @@ -0,0 +1,87 @@ +array( + 'method'=> 'POST', + 'header'=> implode("\r\n", array( + 'Content-type: application/x-www-form-urlencoded', + 'Accept: application/xrds+xml, */*' + )), + 'content' => http_build_query($params, '', '&') + ))); + + $response = file_get_contents('https://www.google.com/accounts/o8/ud', false, $context); + $identity = $_GET['openid_identity']; + + return array(strpos($response, 'is_valid:true') !== false, $identity); +} + + +function mozilla_validate($token) +{ + if (! ini_get('allow_url_fopen')) { + die('You must have "allow_url_fopen=On" to use this feature!'); + } + + $params = array( + 'assertion' => $token, + 'audience' => (isset($_SERVER['HTTPS']) ? 'https://' : 'http://').$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'] + ); + + $context = stream_context_create(array( + 'http'=> array( + 'method'=> 'POST', + 'header'=> implode("\r\n", array( + 'Content-type: application/x-www-form-urlencoded', + )), + 'content' => http_build_query($params, '', '&') + ))); + + $body = file_get_contents('https://verifier.login.persona.org/verify', false, $context); + $response = json_decode($body, true); + + return array( + $response['status'] === 'okay', + $response['email'] + ); +}