diff --git a/portfolio/googledocs/db/events.php b/admin/oauth2callback.php similarity index 52% rename from portfolio/googledocs/db/events.php rename to admin/oauth2callback.php index b0d3d0c702f..364c0023de0 100644 --- a/portfolio/googledocs/db/events.php +++ b/admin/oauth2callback.php @@ -1,4 +1,5 @@ . /** - * Add event handlers for the googledocs portfolio. + * An oauth2 redirection endpoint which can be used for an application: + * http://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-3.1.2 * - * @package portfolio_googledocs - * @category event - * @copyright 2009 Penny Leach + * This is used because some oauth servers will not allow a redirect urls + * with get params (like repository callback) and that needs to be called + * using the state param. + * + * @package core + * @copyright 2012 Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$handlers = array ( - 'user_deleted' => array ( - 'handlerfile' => '/portfolio/googledocs/lib.php', - 'handlerfunction' => 'portfolio_googledocs_user_deleted', - 'schedule' => 'cron', - 'internal' => 0, - ), -); +require_once(dirname(dirname(__FILE__)).'/config.php'); +// The authorization code generated by the authorization server. +$code = required_param('code', PARAM_RAW); +// The state parameter we've given (used in moodle as a redirect url). +$state = required_param('state', PARAM_URL); +redirect(new moodle_url($state, array('code' => $code))); diff --git a/lib/googleapi.php b/lib/googleapi.php index a079ffb5d2f..5994dd382d8 100644 --- a/lib/googleapi.php +++ b/lib/googleapi.php @@ -1,276 +1,62 @@ . + /** - * Moodle - Modular Object-Oriented Dynamic Learning Environment - * http://moodle.org - * Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com + * Simple implementation of some Google API functions for Moodle. * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. + * @package core + * @copyright Dan Poltawski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/filelib.php'); +require_once($CFG->libdir.'/oauthlib.php'); + +/** + * Class for manipulating google documents through the google data api. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * Docs for this can be found here: + * {@link http://code.google.com/apis/documents/docs/2.0/developers_guide_protocol.html} * * @package core * @subpackage lib * @copyright Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * - * Simple implementation of some Google API functions for Moodle. - */ - -defined('MOODLE_INTERNAL') || die(); - - /** Include essential file */ -require_once($CFG->libdir.'/filelib.php'); - -/** - * Base class for google authenticated http requests - * - * Most Google API Calls required that requests are sent with an - * Authorization header + token. This class extends the curl class - * to aid this - * - * @package moodlecore - * @subpackage lib - * @copyright Dan Poltawski - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -abstract class google_auth_request extends curl { - protected $token = ''; - private $persistantheaders = array(); - - // Must be overridden with the authorization header name - public static function get_auth_header_name() { - throw new coding_exception('get_auth_header_name() method needs to be overridden in each subclass of google_auth_request'); - } - - protected function request($url, $options = array()){ - if($this->token){ - // Adds authorisation head to a request so that it can be authentcated - $this->setHeader('Authorization: '. $this->get_auth_header_name().'"'.$this->token.'"'); - } - - foreach($this->persistantheaders as $h){ - $this->setHeader($h); - } - - $ret = parent::request($url, $options); - // reset headers for next request - $this->header = array(); - return $ret; - } - - protected function multi($requests, $options = array()) { - if($this->token){ - // Adds authorisation head to a request so that it can be authentcated - $this->setHeader('Authorization: '. $this->get_auth_header_name().'"'.$this->token.'"'); - } - - foreach($this->persistantheaders as $h){ - $this->setHeader($h); - } - - $ret = parent::multi($requests, $options); - // reset headers for next request - $this->header = array(); - return $ret; - } - - public function get_sessiontoken(){ - return $this->token; - } - - public function add_persistant_header($header){ - $this->persistantheaders[] = $header; - } -} - -/******* - * The following two classes are usd to implement AuthSub google - * authtentication, as documented here: - * http://code.google.com/apis/accounts/docs/AuthSub.html - *******/ - -/** - * Used to uprade a google AuthSubRequest one-time token into - * a session token which can be used long term. - * - * @package moodlecore - * @subpackage lib - * @copyright Dan Poltawski - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class google_authsub_request extends google_auth_request { - const AUTHSESSION_URL = 'https://www.google.com/accounts/AuthSubSessionToken'; - - /** - * Constructor. Calls constructor of its parents - * - * @param string $authtoken The token to upgrade to a session token - */ - public function __construct($authtoken){ - parent::__construct(); - $this->token = $authtoken; - } - - /** - * Requests a long-term session token from google based on the - * - * @return string Sub-Auth token - */ - public function get_session_token(){ - $content = $this->get(google_authsub_request::AUTHSESSION_URL); - - if( preg_match('/token=(.*)/i', $content, $matches) ){ - return $matches[1]; - }else{ - throw new moodle_exception('could not upgrade google authtoken to session token'); - } - } - - public static function get_auth_header_name(){ - return 'AuthSub token='; - } -} - -/** - * Allows http calls using google subauth authorisation - * - * @package moodlecore - * @subpackage lib - * @copyright Dan Poltawski - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class google_authsub extends google_auth_request { - const LOGINAUTH_URL = 'https://www.google.com/accounts/AuthSubRequest'; - const VERIFY_TOKEN_URL = 'https://www.google.com/accounts/AuthSubTokenInfo'; - const REVOKE_TOKEN_URL = 'https://www.google.com/accounts/AuthSubRevokeToken'; - - /** - * Constructor, allows subauth requests using the response from an initial - * AuthSubRequest or with the subauth long-term token. Note that constructing - * this object without a valid token will cause an exception to be thrown. - * - * @param string $sessiontoken A long-term subauth session token - * @param string $authtoken A one-time auth token wich is used to upgrade to session token - * @param mixed @options Options to pass to the base curl object - */ - public function __construct($sessiontoken = '', $authtoken = '', $options = array()){ - parent::__construct($options); - - if( $authtoken ){ - $gauth = new google_authsub_request($authtoken); - $sessiontoken = $gauth->get_session_token(); - } - - $this->token = $sessiontoken; - if(! $this->valid_token() ){ - throw new moodle_exception('Invalid subauth token'); - } - } - - /** - * Tests if a subauth token used is valid - * - * @return boolean true if token valid - */ - public function valid_token(){ - $this->get(google_authsub::VERIFY_TOKEN_URL); - - if($this->info['http_code'] === 200){ - return true; - }else{ - return false; - } - } - - /** - * Calls googles api to revoke the subauth token - * - * @return boolean Returns true if token succesfully revoked - */ - public function revoke_session_token(){ - $this->get(google_authsub::REVOKE_TOKEN_URL); - - if($this->info['http_code'] === 200){ - $this->token = ''; - return true; - }else{ - return false; - } - } - - /** - * Creates a login url for subauth request - * - * @param string $returnaddr The address which the user should be redirected to recieve the token - * @param string $realm The google realm which is access is being requested - * @return string URL to bounce the user to - */ - public static function login_url($returnaddr, $realm){ - $uri = google_authsub::LOGINAUTH_URL.'?next=' - .urlencode($returnaddr) - .'&scope=' - .urlencode($realm) - .'&session=1&secure=0'; - - return $uri; - } - - public static function get_auth_header_name(){ - return 'AuthSub token='; - } -} - -/** - * Class for manipulating google documents through the google data api - * Docs for this can be found here: - * {@link http://code.google.com/apis/documents/docs/2.0/developers_guide_protocol.html} - * - * @package moodlecore - * @subpackage lib - * @copyright Dan Poltawski - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class google_docs { - // need both docs and the spreadsheets realm + /** @var string Realm for authentication, need both docs and spreadsheet realm */ const REALM = 'https://docs.google.com/feeds/ https://spreadsheets.google.com/feeds/ https://docs.googleusercontent.com/'; + /** @var string Document list url */ const DOCUMENTFEED_URL = 'https://docs.google.com/feeds/default/private/full'; - const USER_PREF_NAME = 'google_authsub_sesskey'; + /** @var string Upload url */ + const UPLOAD_URL = 'https://docs.google.com/feeds/upload/create-session/default/private/full?convert=false'; - private $google_curl = null; + /** @var google_oauth oauth curl class for making authenticated requests */ + private $googleoauth = null; /** * Constructor. * - * @param object A google_auth_request object which can be used to do http requests + * @param google_oauth $googleoauth oauth curl class for making authenticated requests */ - public function __construct($google_curl){ - if(is_a($google_curl, 'google_auth_request')){ - $this->google_curl = $google_curl; - $this->google_curl->add_persistant_header('GData-Version: 3.0'); - }else{ - throw new moodle_exception('Google Curl Request object not given'); - } - } - - public static function get_sesskey($userid){ - return get_user_preferences(google_docs::USER_PREF_NAME, false, $userid); - } - - public static function set_sesskey($value, $userid){ - return set_user_preference(google_docs::USER_PREF_NAME, $value, $userid); - } - - public static function delete_sesskey($userid){ - return unset_user_preference(google_docs::USER_PREF_NAME, $userid); + public function __construct(google_oauth $googleoauth) { + $this->googleoauth = $googleoauth; + $this->googleoauth->setHeader('GData-Version: 3.0'); } /** @@ -279,21 +65,19 @@ class google_docs { * @param string $search A search string to do full text search on the documents * @return mixed Array of files formated for fileapoi */ - #FIXME - public function get_file_list($search = ''){ + public function get_file_list($search = '') { global $CFG, $OUTPUT; - $url = google_docs::DOCUMENTFEED_URL; + $url = self::DOCUMENTFEED_URL; - if($search){ + if ($search) { $url.='?q='.urlencode($search); } - $content = $this->google_curl->get($url); + $content = $this->googleoauth->get($url); $xml = new SimpleXMLElement($content); - $files = array(); - foreach($xml->entry as $gdoc){ + foreach ($xml->entry as $gdoc) { $docid = (string) $gdoc->children('http://schemas.google.com/g/2005')->resourceId; list($type, $docid) = explode(':', $docid); @@ -319,9 +103,13 @@ class google_docs { $title = (string)$gdoc->title; $source = (string)$gdoc->content[0]->attributes()->src; break; + case 'file': + $title = (string)$gdoc->title; + $source = (string)$gdoc->content[0]->attributes()->src; + break; } - if(!empty($source)){ + if (!empty($source)) { $files[] = array( 'title' => $title, 'url' => "{$gdoc->link[0]->attributes()->href}", 'source' => $source, @@ -340,71 +128,98 @@ class google_docs { * @param object $file File object * @return boolean True on success */ - public function send_file($file){ - $this->google_curl->setHeader("Content-Length: ". $file->get_filesize()); - $this->google_curl->setHeader("Content-Type: ". $file->get_mimetype()); - $this->google_curl->setHeader("Slug: ". $file->get_filename()); + public function send_file($file) { + // First we create the 'resumable upload request'. + $this->googleoauth->setHeader("Content-Length: 0"); + $this->googleoauth->setHeader("X-Upload-Content-Length: ". $file->get_filesize()); + $this->googleoauth->setHeader("X-Upload-Content-Type: ". $file->get_mimetype()); + $this->googleoauth->setHeader("Slug: ". $file->get_filename()); + $this->googleoauth->post(self::UPLOAD_URL); - $this->google_curl->post(google_docs::DOCUMENTFEED_URL, $file->get_content()); + if ($this->googleoauth->info['http_code'] !== 200) { + throw new moodle_exception('Cantpostupload'); + } - if($this->google_curl->info['http_code'] === 201){ + // Now we http PUT the file in the location returned. + $location = $this->googleoauth->response['Location']; + if (empty($location)) { + throw new moodle_exception('Nouploadlocation'); + } + + // Reset the curl object for actually sending the file. + $this->googleoauth->clear_headers(); + $this->googleoauth->setHeader("Content-Length: ". $file->get_filesize()); + $this->googleoauth->setHeader("Content-Type: ". $file->get_mimetype()); + + // We can't get a filepointer, so have to copy the file.. + $tmproot = make_temp_directory('googledocsuploads'); + $tmpfilepath = $tmproot.'/'.$file->get_contenthash(); + $file->copy_content_to($tmpfilepath); + + // HTTP PUT the file. + $this->googleoauth->put($location, array('file'=>$tmpfilepath)); + + // Remove the temporary file we created.. + unlink($tmpfilepath); + + if ($this->googleoauth->info['http_code'] === 201) { return true; - }else{ + } else { return false; } } - public function download_file($url, $fp){ - return $this->google_curl->download(array( array('url'=>$url, 'file' => $fp) )); + /** + * Downloads a file using authentication + * + * @param string $url url of file + * @param string $path path to save file to + * @return array stucture for repository download_file + */ + public function download_file($url, $path) { + $content = $this->googleoauth->get($url); + file_put_contents($path, $content); + return array('path'=>$path, 'url'=>$url); } } /** - * Class for manipulating picasa through the google data api + * Class for manipulating picasa through the google data api. + * * Docs for this can be found here: * {@link http://code.google.com/apis/picasaweb/developers_guide_protocol.html} * - * @package moodlecore - * @subpackage lib + * @package core * @copyright Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class google_picasa { + /** @var string Realm for authentication */ const REALM = 'http://picasaweb.google.com/data/'; - const USER_PREF_NAME = 'google_authsub_sesskey_picasa'; + /** @var string Upload url */ const UPLOAD_LOCATION = 'https://picasaweb.google.com/data/feed/api/user/default/albumid/default'; + /** @var string photo list url */ const ALBUM_PHOTO_LIST = 'https://picasaweb.google.com/data/feed/api/user/default/albumid/'; + /** @var string search url */ const PHOTO_SEARCH_URL = 'https://picasaweb.google.com/data/feed/api/user/default?kind=photo&q='; + /** @var string album list url */ const LIST_ALBUMS_URL = 'https://picasaweb.google.com/data/feed/api/user/default'; + /** @var string manage files url */ const MANAGE_URL = 'http://picasaweb.google.com/'; - private $google_curl = null; + /** @var google_oauth oauth curl class for making authenticated requests */ + private $googleoauth = null; + /** @var string Last album name retrievied */ private $lastalbumname = null; /** * Constructor. * - * @param object A google_auth_request object which can be used to do http requests + * @param google_oauth $googleoauth oauth curl class for making authenticated requests */ - public function __construct($google_curl){ - if(is_a($google_curl, 'google_auth_request')){ - $this->google_curl = $google_curl; - $this->google_curl->add_persistant_header('GData-Version: 2'); - }else{ - throw new moodle_exception('Google Curl Request object not given'); - } - } - - public static function get_sesskey($userid){ - return get_user_preferences(google_picasa::USER_PREF_NAME, false, $userid); - } - - public static function set_sesskey($value, $userid){ - return set_user_preference(google_picasa::USER_PREF_NAME, $value, $userid); - } - - public static function delete_sesskey($userid){ - return unset_user_preference(google_picasa::USER_PREF_NAME, $userid); + public function __construct(google_oauth $googleoauth) { + $this->googleoauth = $googleoauth; + $this->googleoauth->setHeader('GData-Version: 2'); } /** @@ -413,16 +228,16 @@ class google_picasa { * @param object $file File object * @return boolean True on success */ - public function send_file($file){ - $this->google_curl->setHeader("Content-Length: ". $file->get_filesize()); - $this->google_curl->setHeader("Content-Type: ". $file->get_mimetype()); - $this->google_curl->setHeader("Slug: ". $file->get_filename()); + public function send_file($file) { + $this->googleoauth->setHeader("Content-Length: ". $file->get_filesize()); + $this->googleoauth->setHeader("Content-Type: ". $file->get_mimetype()); + $this->googleoauth->setHeader("Slug: ". $file->get_filename()); - $this->google_curl->post(google_picasa::UPLOAD_LOCATION, $file->get_content()); + $this->googleoauth->post(self::UPLOAD_LOCATION, $file->get_content()); - if($this->google_curl->info['http_code'] === 201){ + if ($this->googleoauth->info['http_code'] === 201) { return true; - }else{ + } else { return false; } } @@ -435,10 +250,10 @@ class google_picasa { * @param string $path The path to files (assumed to be albumid) * @return mixed $files A list of files for the file picker */ - public function get_file_list($path = ''){ - if(!$path){ + public function get_file_list($path = '') { + if (!$path) { return $this->get_albums(); - }else{ + } else { return $this->get_album_photos($path); } } @@ -449,8 +264,8 @@ class google_picasa { * @param int $albumid Photo album to list photos from * @return mixed $files A list of files for the file picker */ - public function get_album_photos($albumid){ - $albumcontent = $this->google_curl->get(google_picasa::ALBUM_PHOTO_LIST.$albumid); + public function get_album_photos($albumid) { + $albumcontent = $this->googleoauth->get(self::ALBUM_PHOTO_LIST.$albumid); return $this->get_photo_details($albumcontent); } @@ -471,8 +286,8 @@ class google_picasa { * @param string $query Search terms * @return mixed $files A list of files for the file picker */ - public function do_photo_search($query){ - $content = $this->google_curl->get(google_picasa::PHOTO_SEARCH_URL.htmlentities($query)); + public function do_photo_search($query) { + $content = $this->googleoauth->get(self::PHOTO_SEARCH_URL.htmlentities($query)); return $this->get_photo_details($content); } @@ -483,17 +298,17 @@ class google_picasa { * * @return mixes $files Array in the format get_listing uses for folders */ - public function get_albums(){ - $content = $this->google_curl->get(google_picasa::LIST_ALBUMS_URL); + public function get_albums() { + $content = $this->googleoauth->get(self::LIST_ALBUMS_URL); $xml = new SimpleXMLElement($content); $files = array(); - foreach($xml->entry as $album){ + foreach ($xml->entry as $album) { $gphoto = $album->children('http://schemas.google.com/photos/2007'); $mediainfo = $album->children('http://search.yahoo.com/mrss/'); - //hacky... + // Hacky... $thumbnailinfo = $mediainfo->group->thumbnail[0]->attributes(); $files[] = array( 'title' => (string) $album->title, @@ -501,7 +316,7 @@ class google_picasa { 'size' => (int) $gphoto->bytesUsed, 'path' => (string) $gphoto->id, 'thumbnail' => (string) $thumbnailinfo['url'], - 'thumbnail_width' => 160, // 160 is the native maximum dimension + 'thumbnail_width' => 160, // 160 is the native maximum dimension. 'thumbnail_height' => 160, 'children' => array(), ); @@ -518,22 +333,22 @@ class google_picasa { * @param string $rawxml XML from picasa api * @return mixed $files A list of files for the file picker */ - public function get_photo_details($rawxml){ + public function get_photo_details($rawxml) { $xml = new SimpleXMLElement($rawxml); $this->lastalbumname = (string)$xml->title; $files = array(); - foreach($xml->entry as $photo){ + foreach ($xml->entry as $photo) { $gphoto = $photo->children('http://schemas.google.com/photos/2007'); $mediainfo = $photo->children('http://search.yahoo.com/mrss/'); $fullinfo = $mediainfo->group->content->attributes(); - //hacky... + // Hacky... $thumbnailinfo = $mediainfo->group->thumbnail[0]->attributes(); - // Derive the nicest file name we can + // Derive the nicest file name we can. if (!empty($mediainfo->group->description)) { $title = shorten_text((string)$mediainfo->group->description, 20, false, ''); $title = clean_filename($title).'.jpg'; @@ -547,7 +362,7 @@ class google_picasa { 'size' => (int) $gphoto->size, 'path' => $gphoto->albumid.'/'.$gphoto->id, 'thumbnail' => (string) $thumbnailinfo['url'], - 'thumbnail_width' => 72, // 72 is the native maximum dimension + 'thumbnail_width' => 72, // 72 is the native maximum dimension. 'thumbnail_height' => 72, 'source' => (string) $fullinfo['url'], 'url' => (string) $fullinfo['url'] @@ -556,54 +371,36 @@ class google_picasa { return $files; } - } /** - * Beginings of an implementation of Clientogin authenticaton for google - * accounts as documented here: - * {@link http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html#ClientLogin} + * OAuth 2.0 client for Google Services * - * With this authentication we have to accept a username and password and to post - * it to google. Retrieving a token for use afterwards. - * - * @package moodlecore - * @subpackage lib - * @copyright Dan Poltawski + * @package core + * @copyright 2012 Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class google_authclient extends google_auth_request { - const LOGIN_URL = 'https://www.google.com/accounts/ClientLogin'; - - public function __construct($sessiontoken = '', $username = '', $password = '', $options = array() ){ - parent::__construct($options); - - if($username and $password){ - $param = array( - 'accountType'=>'GOOGLE', - 'Email'=>$username, - 'Passwd'=>$password, - 'service'=>'writely' - ); - - $content = $this->post(google_authclient::LOGIN_URL, $param); - - if( preg_match('/auth=(.*)/i', $content, $matches) ){ - $sessiontoken = $matches[1]; - }else{ - throw new moodle_exception('could not upgrade authtoken'); - } - - } - - if($sessiontoken){ - $this->token = $sessiontoken; - }else{ - throw new moodle_exception('no session token specified'); - } +class google_oauth extends oauth2_client { + /** + * Returns the auth url for OAuth 2.0 request + * @return string the auth url + */ + protected function auth_url() { + return 'https://accounts.google.com/o/oauth2/auth'; } - public static function get_auth_header_name(){ - return 'GoogleLogin auth='; + /** + * Returns the token url for OAuth 2.0 request + * @return string the auth url + */ + protected function token_url() { + return 'https://accounts.google.com/o/oauth2/token'; + } + + /** + * Clear any headers in the curl object + */ + public function clear_headers() { + $this->header = array(); } } diff --git a/lib/oauthlib.php b/lib/oauthlib.php index 36ee14e9f9b..b4aa15b7500 100644 --- a/lib/oauthlib.php +++ b/lib/oauthlib.php @@ -1,5 +1,4 @@ . + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir.'/filelib.php'); + /** * OAuth helper class * @@ -349,3 +353,259 @@ class oauth_helper { return md5($mt . $rand); } } + +/** + * OAuth 2.0 Client for using web access tokens. + * + * http://tools.ietf.org/html/draft-ietf-oauth-v2-22 + * + * @package core + * @copyright Dan Poltawski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class oauth2_client extends curl { + /** var string client identifier issued to the client */ + private $clientid = ''; + /** var string The client secret. */ + private $clientsecret = ''; + /** var string URL to return to after authenticating */ + private $returnurl = ''; + /** var string scope of the authentication request */ + private $scope = ''; + /** var stdClass access token object */ + private $accesstoken = null; + + /** + * Returns the auth url for OAuth 2.0 request + * @return string the auth url + */ + abstract protected function auth_url(); + + /** + * Returns the token url for OAuth 2.0 request + * @return string the auth url + */ + abstract protected function token_url(); + + /** + * Constructor. + * + * @param string $clientid + * @param string $clientsecret + * @param string $returnurl + * @param string $scope + */ + public function __construct($clientid, $clientsecret, $returnurl, $scope) { + parent::__construct(); + $this->clientid = $clientid; + $this->clientsecret = $clientsecret; + $this->returnurl = $returnurl; + $this->scope = $scope; + $this->accesstoken = $this->get_stored_token(); + } + + /** + * Is the user logged in? Note that if this is called + * after the first part of the authorisation flow the token + * is upgraded to an accesstoken. + * + * @return boolean true if logged in + */ + public function is_logged_in() { + // Has the token expired? + if (isset($this->accesstoken->expires) && time() >= $this->accesstoken->expires) { + $this->log_out(); + return false; + } + + // We have a token so we are logged in. + if (isset($this->accesstoken->token)) { + return true; + } + + // If we've been passed then authorization code generated by the + // authorization server try and upgrade the token to an access token. + $code = optional_param('code', null, PARAM_RAW); + if ($code && $this->upgrade_token($code)) { + return true; + } + + return false; + } + + /** + * Callback url where the request is returned to. + * + * @return moodle_url url of callback + */ + public static function callback_url() { + global $CFG; + + return new moodle_url('/admin/oauth2callback.php'); + } + + /** + * Returns the login link for this oauth request + * + * @return moodle_url login url + */ + public function get_login_url() { + + $callbackurl = self::callback_url(); + $url = new moodle_url($this->auth_url(), + array('client_id' => $this->clientid, + 'response_type' => 'code', + 'redirect_uri' => $callbackurl->out(false), + 'state' => $this->returnurl, + 'scope' => $this->scope, + )); + + return $url; + } + + /** + * Upgrade a authorization token from oauth 2.0 to an access token + * + * @param string $code the code returned from the oauth authenticaiton + * @return boolean true if token is upgraded succesfully + */ + public function upgrade_token($code) { + $callbackurl = self::callback_url(); + $params = array('client_id' => $this->clientid, + 'client_secret' => $this->clientsecret, + 'grant_type' => 'authorization_code', + 'code' => $code, + 'redirect_uri' => $callbackurl->out(false), + ); + + // Requests can either use http GET or POST. + if ($this->use_http_get()) { + $response = $this->get($this->token_url(), $params); + } else { + $response = $this->post($this->token_url(), $params); + } + + if (!$this->info['http_code'] === 200) { + throw new moodle_exception('Could not upgrade oauth token'); + } + + $r = json_decode($response); + + if (!isset($r->access_token)) { + return false; + } + + // Store the token an expiry time. + $accesstoken = new stdClass; + $accesstoken->token = $r->access_token; + $accesstoken->expires = (time() + ($r->expires_in - 10)); // Expires 10 seconds before actual expiry. + $this->store_token($accesstoken); + + return true; + } + + /** + * Logs out of a oauth request, clearing any stored tokens + */ + public function log_out() { + $this->store_token(null); + } + + /** + * Make a HTTP request, adding the access token we have + * + * @param string $url The URL to request + * @param array $options + * @return bool + */ + protected function request($url, $options = array()) { + $murl = new moodle_url($url); + + if ($this->accesstoken) { + if ($this->use_http_get()) { + // If using HTTP GET add as a parameter. + $murl->param('access_token', $this->accesstoken->token); + } else { + $this->setHeader('Authorization: Bearer '.$this->accesstoken->token); + } + } + + return parent::request($murl->out(false), $options); + } + + /** + * Multiple HTTP Requests + * This function could run multi-requests in parallel. + * + * @param array $requests An array of files to request + * @param array $options An array of options to set + * @return array An array of results + */ + protected function multi($requests, $options = array()) { + if ($this->accesstoken) { + $this->setHeader('Authorization: Bearer '.$this->accesstoken->token); + } + return parent::multi($requests, $options); + } + + /** + * Returns the tokenname for the access_token to be stored + * through multiple requests. + * + * The default implentation is to use the classname combiend + * with the scope. + * + * @return string tokenname for prefernce storage + */ + protected function get_tokenname() { + // This is unusual but should work for most purposes. + return get_class($this).'-'.md5($this->scope); + } + + /** + * Store a token between requests. Currently uses + * session named by get_tokenname + * + * @param stdClass|null $token token object to store or null to clear + */ + protected function store_token($token) { + global $SESSION; + + $this->accesstoken = $token; + $name = $this->get_tokenname(); + + if ($token !== null) { + $SESSION->{$name} = $token; + } else { + unset($SESSION->{$name}); + } + } + + /** + * Retrieve a token stored. + * + * @return stdClass|null token object + */ + protected function get_stored_token() { + global $SESSION; + + $name = $this->get_tokenname(); + + if (isset($SESSION->{$name})) { + return $SESSION->{$name}; + } + + return null; + } + + /** + * Should HTTP GET be used instead of POST? + * Some APIs do not support POST and want oauth to use + * GET instead (with the auth_token passed as a GET param). + * + * @return bool true if GET should be used + */ + protected function use_http_get() { + return false; + } +} diff --git a/lib/portfolio/forms.php b/lib/portfolio/forms.php index ba408f78486..7b7c66908ed 100644 --- a/lib/portfolio/forms.php +++ b/lib/portfolio/forms.php @@ -107,7 +107,7 @@ final class portfolio_export_form extends moodleform { * @param stdClass $data portfolio information from form data * @return array */ - public function validation($data) { + public function validation($data, $files) { $errors = array(); @@ -228,7 +228,7 @@ final class portfolio_admin_form extends moodleform { * @param stdObject $data form data * @return array */ - public function validation($data) { + public function validation($data, $files) { global $DB; $errors = array(); @@ -296,7 +296,7 @@ final class portfolio_user_form extends moodleform { * * @param stdClass $data form data */ - public function validation($data) { + public function validation($data, $files) { $errors = $this->instance->user_config_validation($data); diff --git a/portfolio/add.php b/portfolio/add.php index b3c3982df0a..b5a1c4a4e25 100644 --- a/portfolio/add.php +++ b/portfolio/add.php @@ -221,7 +221,8 @@ if (!$exporter->get('instance')) { throw new portfolio_export_exception($exporter, 'noavailableplugins', 'portfolio'); } else if (count($options) == 1) { // no point displaying a form, just redirect. - $instance = array_shift(array_keys($options)); + $optionskeys = array_keys($options); + $instance = array_shift($optionskeys); redirect($CFG->wwwroot . '/portfolio/add.php?id= ' . $exporter->get('id') . '&instance=' . $instance . '&sesskey=' . sesskey()); } // be very selective about not including this unless we really need to diff --git a/portfolio/googledocs/db/upgrade.php b/portfolio/googledocs/db/upgrade.php new file mode 100644 index 00000000000..dfdc1dca43a --- /dev/null +++ b/portfolio/googledocs/db/upgrade.php @@ -0,0 +1,67 @@ +. + +/** + * @param int $oldversion the version we are upgrading from + * @return bool result + */ +function xmldb_portfolio_googledocs_upgrade($oldversion) { + global $CFG, $DB; + + $dbman = $DB->get_manager(); + + if ($oldversion < 2012053000) { + // Delete old user preferences containing authsub tokens. + $DB->delete_records('user_preferences', array('name' => 'google_authsub_sesskey')); + upgrade_plugin_savepoint(true, 2012053000, 'portfolio', 'googledocs'); + } + + if ($oldversion < 2012053001) { + $existing = $DB->get_record('portfolio_instance', array('plugin' => 'googledocs'), '*', IGNORE_MULTIPLE); + + if ($existing) { + portfolio_googledocs_admin_upgrade_notification(); + } + + upgrade_plugin_savepoint(true, 2012053001, 'portfolio', 'googledocs'); + } + + return true; +} + +function portfolio_googledocs_admin_upgrade_notification() { + $admins = get_admins(); + + if (empty($admins)) { + return; + } + $mainadmin = reset($admins); + + foreach ($admins as $admin) { + $message = new stdClass(); + $message->component = 'moodle'; + $message->name = 'notices'; + $message->userfrom = $mainadmin; + $message->userto = $admin; + $message->smallmessage = get_string('oauth2upgrade_message_small', 'portfolio_googledocs'); + $message->subject = get_string('oauth2upgrade_message_subject', 'portfolio_googledocs'); + $message->fullmessage = get_string('oauth2upgrade_message_content', 'portfolio_googledocs'); + $message->fullmessagehtml = get_string('oauth2upgrade_message_content', 'portfolio_googledocs'); + $message->fullmessageformat = FORMAT_PLAIN; + $message->notification = 1; + message_send($message); + } +} diff --git a/portfolio/googledocs/lang/en/portfolio_googledocs.php b/portfolio/googledocs/lang/en/portfolio_googledocs.php index f1c9262f823..c52bba14df5 100644 --- a/portfolio/googledocs/lang/en/portfolio_googledocs.php +++ b/portfolio/googledocs/lang/en/portfolio_googledocs.php @@ -1,5 +1,4 @@ To use the google docs portfolio you must be registered with Google. Instructions for registing your installation with Google are described in Moodle Docs. The redirect url should be set to:

{$a->callbackurl}

'; +$string['oauth2upgrade_message_subject'] = 'Important information regarding Google Docs portfolio plugin'; +$string['oauth2upgrade_message_content'] = 'As part of the upgrade to Moodle 2.3, the Google Docs portfolio plugin has been disabled due to changes in Googles API. To re-enable your plugin, you must configure oauth credentials in this plugin.'; +$string['oauth2upgrade_message_small'] = 'The Google Docs portfolio plugin has been disabled until configured with OAuth2'; $string['pluginname'] = 'Google Docs'; $string['sendfailed'] = 'The file {$a} failed to transfer to google'; +$string['secret'] = 'Secret'; diff --git a/portfolio/googledocs/lib.php b/portfolio/googledocs/lib.php index 2223f9d4831..9820246f9db 100644 --- a/portfolio/googledocs/lib.php +++ b/portfolio/googledocs/lib.php @@ -1,4 +1,19 @@ . + /** * Google Documents Portfolio Plugin * @@ -9,18 +24,10 @@ require_once($CFG->libdir.'/portfolio/plugin.php'); require_once($CFG->libdir.'/googleapi.php'); class portfolio_plugin_googledocs extends portfolio_plugin_push_base { - private $sessiontoken; + private $googleoauth = null; public function supported_formats() { - return array( - PORTFOLIO_FORMAT_PLAINHTML, - PORTFOLIO_FORMAT_IMAGE, - PORTFOLIO_FORMAT_TEXT, - PORTFOLIO_FORMAT_PDF, - PORTFOLIO_FORMAT_DOCUMENT, - PORTFOLIO_FORMAT_PRESENTATION, - PORTFOLIO_FORMAT_SPREADSHEET - ); + return array(PORTFOLIO_FORMAT_FILE); } public static function get_name() { @@ -28,29 +35,27 @@ class portfolio_plugin_googledocs extends portfolio_plugin_push_base { } public function prepare_package() { - // we send the files as they are, no prep required + // We send the files as they are, no prep required. return true; } - public function get_interactive_continue_url(){ + public function get_interactive_continue_url() { return 'http://docs.google.com/'; } public function expected_time($callertime) { - // we trust what the portfolio says + // We trust what the portfolio says. return $callertime; } public function send_package() { - - if(!$this->sessiontoken){ - throw new portfolio_plugin_exception('nosessiontoken', 'portfolio_googledocs'); + if (!$this->googleoauth) { + throw new portfolio_plugin_exception('noauthtoken', 'portfolio_googledocs'); } - $gdocs = new google_docs(new google_authsub($this->sessiontoken)); - + $gdocs = new google_docs($this->googleoauth); foreach ($this->exporter->get_tempfiles() as $file) { - if(!$gdocs->send_file($file)){ + if (!$gdocs->send_file($file)) { throw new portfolio_plugin_exception('sendfailed', 'portfolio_gdocs', $file->get_filename()); } } @@ -62,20 +67,12 @@ class portfolio_plugin_googledocs extends portfolio_plugin_push_base { return false; } - $sesskey = google_docs::get_sesskey($this->get('user')->id); - - if($sesskey){ - try{ - $gauth = new google_authsub($sesskey); - $this->sessiontoken = $sesskey; - return false; - }catch(Exception $e){ - // sesskey is not valid, delete store and re-auth - google_docs::delete_sesskey($this->get('user')->id); - } + $this->initialize_oauth(); + if ($this->googleoauth->is_logged_in()) { + return false; + } else { + return $this->googleoauth->get_login_url(); } - - return google_authsub::login_url($CFG->wwwroot.'/portfolio/add.php?postcontrol=1&id=' . $this->exporter->get('id') . '&sesskey=' . sesskey(), google_docs::REALM); } public function post_control($stage, $params) { @@ -83,43 +80,62 @@ class portfolio_plugin_googledocs extends portfolio_plugin_push_base { return; } - if(!array_key_exists('token', $params)){ - throw new portfolio_plugin_exception('noauthtoken', 'portfolio_googledocs'); + $this->initialize_oauth(); + if ($this->googleoauth->is_logged_in()) { + return false; + } else { + return $this->googleoauth->get_login_url(); } - - // we now have our auth token, get a session token.. - $gauth = new google_authsub(false, $params['token']); - $this->sessiontoken = $gauth->get_sessiontoken(); - - google_docs::set_sesskey($this->sessiontoken, $this->get('user')->id); } public static function allows_multiple_instances() { return false; } -} -/** - * Registers to the user_deleted event to revoke any - * subauth tokens we have from them - * - * @param $user user object - * @return boolean true in all cases as its only minor cleanup - */ -function portfolio_googledocs_user_deleted($user){ - // it is only by luck that the user prefstill exists now? - // We probably need a pre-delete event? - if($sesskey = google_docs::get_sesskey($user->id)){ - try{ - $gauth = new google_authsub($sesskey); - - $gauth->revoke_session_token(); - }catch(Exception $e){ - // we don't care that much about success- just being good - // google api citzens - return true; - } + public static function has_admin_config() { + return true; } - return true; + public static function get_allowed_config() { + return array('clientid', 'secret'); + } + + public function admin_config_form(&$mform) { + $a = new stdClass; + $a->docsurl = get_docs_url('Google_OAuth2_Setup'); + $a->callbackurl = google_oauth::callback_url()->out(false); + + $mform->addElement('static', null, '', get_string('oauthinfo', 'portfolio_googledocs', $a)); + + $mform->addElement('text', 'clientid', get_string('clientid', 'portfolio_googledocs')); + $mform->addElement('text', 'secret', get_string('secret', 'portfolio_googledocs')); + + $strrequired = get_string('required'); + $mform->addRule('clientid', $strrequired, 'required', null, 'client'); + $mform->addRule('secret', $strrequired, 'required', null, 'client'); + } + + private function initialize_oauth() { + $returnurl = new moodle_url('/portfolio/add.php'); + $returnurl->param('postcontrol', 1); + $returnurl->param('id', $this->exporter->get('id')); + $returnurl->param('sesskey', sesskey()); + + $clientid = $this->get_config('clientid'); + $secret = $this->get_config('secret'); + + $this->googleoauth = new google_oauth($clientid, $secret, $returnurl->out(false), google_docs::REALM); + } + + public function instance_sanity_check() { + $clientid = $this->get_config('clientid'); + $secret = $this->get_config('secret'); + + // If there is no oauth config (e.g. plugins upgraded from < 2.3 then + // there will be no config and this plugin should be disabled. + if (empty($clientid) or empty($secret)) { + return 'nooauthcredentials'; + } + return 0; + } } diff --git a/portfolio/googledocs/version.php b/portfolio/googledocs/version.php index 20446664e67..5cee5db320b 100644 --- a/portfolio/googledocs/version.php +++ b/portfolio/googledocs/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2011112900; // The current plugin version (Date: YYYYMMDDXX) -$plugin->requires = 2011112900; // Requires this Moodle version -$plugin->component = 'portfolio_googledocs'; // Full name of the plugin (used for diagnostics) +$plugin->version = 2012053001; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2012051100; // Requires this Moodle version. +$plugin->component = 'portfolio_googledocs'; // Full name of the plugin (used for diagnostics). $plugin->cron = 0; diff --git a/portfolio/picasa/db/events.php b/portfolio/picasa/db/events.php deleted file mode 100644 index 09ef7cca20c..00000000000 --- a/portfolio/picasa/db/events.php +++ /dev/null @@ -1,33 +0,0 @@ -. - -/** - * Add event handlers for the picasa portfolio. - * - * @package portfolio_picasa - * @category event - * @copyright 2009 Penny Leach - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -$handlers = array ( - 'user_deleted' => array ( - 'handlerfile' => '/portfolio/picasa/lib.php', - 'handlerfunction' => 'portfolio_picasa_user_deleted', - 'schedule' => 'cron', - 'internal' => 0, - ), -); \ No newline at end of file diff --git a/portfolio/picasa/db/upgrade.php b/portfolio/picasa/db/upgrade.php new file mode 100644 index 00000000000..e5d1cca6d1d --- /dev/null +++ b/portfolio/picasa/db/upgrade.php @@ -0,0 +1,67 @@ +. + +/** + * @param int $oldversion the version we are upgrading from + * @return bool result + */ +function xmldb_portfolio_picasa_upgrade($oldversion) { + global $CFG, $DB; + + $dbman = $DB->get_manager(); + + if ($oldversion < 2012053000) { + // Delete old user preferences containing authsub tokens. + $DB->delete_records('user_preferences', array('name' => 'google_authsub_sesskey_picasa')); + upgrade_plugin_savepoint(true, 2012053000, 'portfolio', 'picasa'); + } + + if ($oldversion < 2012053001) { + $existing = $DB->get_record('portfolio_instance', array('plugin' => 'picasa'), '*', IGNORE_MISSING); + + if ($existing) { + portfolio_picasa_admin_upgrade_notification(); + } + + upgrade_plugin_savepoint(true, 2012053001, 'portfolio', 'picasa'); + } + + return true; +} + +function portfolio_picasa_admin_upgrade_notification() { + $admins = get_admins(); + + if (empty($admins)) { + return; + } + $mainadmin = reset($admins); + + foreach ($admins as $admin) { + $message = new stdClass(); + $message->component = 'moodle'; + $message->name = 'notices'; + $message->userfrom = $mainadmin; + $message->userto = $admin; + $message->smallmessage = get_string('oauth2upgrade_message_small', 'portfolio_picasa'); + $message->subject = get_string('oauth2upgrade_message_subject', 'portfolio_picasa'); + $message->fullmessage = get_string('oauth2upgrade_message_content', 'portfolio_picasa'); + $message->fullmessagehtml = get_string('oauth2upgrade_message_content', 'portfolio_picasa'); + $message->fullmessageformat = FORMAT_PLAIN; + $message->notification = 1; + message_send($message); + } +} diff --git a/portfolio/picasa/lang/en/portfolio_picasa.php b/portfolio/picasa/lang/en/portfolio_picasa.php index fec154b9391..93781b6df96 100644 --- a/portfolio/picasa/lang/en/portfolio_picasa.php +++ b/portfolio/picasa/lang/en/portfolio_picasa.php @@ -1,5 +1,4 @@ To use the Picasa portfolio you must be registered with Google. Instructions for registing your installation with Google are described in Moodle Docs. The redirect url should be set to:

{$a->callbackurl}

'; +$string['oauth2upgrade_message_subject'] = 'Important information regarding Picasa portfolio plugin'; +$string['oauth2upgrade_message_content'] = 'As part of the upgrade to Moodle 2.3, the Picasa portfolio plugin has been disabled due to changes in Googles API. To re-enable your plugin, you must configure oauth credentials in this plugin.'; +$string['oauth2upgrade_message_small'] = 'The Picasa portfolio plugin has been disabled until configured with OAuth2'; $string['pluginname'] = 'Picasa'; $string['sendfailed'] = 'The file {$a} failed to transfer to picasa'; +$string['secret'] = 'Secret'; diff --git a/portfolio/picasa/lib.php b/portfolio/picasa/lib.php index a9ba23fd886..d637b54edca 100644 --- a/portfolio/picasa/lib.php +++ b/portfolio/picasa/lib.php @@ -1,4 +1,19 @@ . + /** * Picasa Portfolio Plugin * @@ -9,7 +24,7 @@ require_once($CFG->libdir.'/portfolio/plugin.php'); require_once($CFG->libdir.'/googleapi.php'); class portfolio_plugin_picasa extends portfolio_plugin_push_base { - private $sessionkey; + private $googleoauth = null; public function supported_formats() { return array(PORTFOLIO_FORMAT_IMAGE, PORTFOLIO_FORMAT_VIDEO); @@ -20,11 +35,11 @@ class portfolio_plugin_picasa extends portfolio_plugin_push_base { } public function prepare_package() { - // we send the files as they are, no prep required + // We send the files as they are, no prep required. return true; } - public function get_interactive_continue_url(){ + public function get_interactive_continue_url() { return 'http://picasaweb.google.com/'; } @@ -33,40 +48,31 @@ class portfolio_plugin_picasa extends portfolio_plugin_push_base { } public function send_package() { - if(!$this->sessionkey){ + if (!$this->googleoauth) { throw new portfolio_plugin_exception('noauthtoken', 'portfolio_picasa'); } - $picasa = new google_picasa(new google_authsub($this->sessionkey)); - + $picasa = new google_picasa($this->googleoauth); foreach ($this->exporter->get_tempfiles() as $file) { - if(!$picasa->send_file($file)){ + if (!$picasa->send_file($file)) { throw new portfolio_plugin_exception('sendfailed', 'portfolio_picasa', $file->get_filename()); } } } public function steal_control($stage) { - global $CFG; if ($stage != PORTFOLIO_STAGE_CONFIG) { return false; } - $sesskey = google_picasa::get_sesskey($this->get('user')->id); + $this->initialize_oauth(); - if($sesskey){ - try{ - $gauth = new google_authsub($sesskey); - $this->sessionkey = $sesskey; - return false; - }catch(Exception $e){ - // sesskey is not valid, delete store and re-auth - google_picasa::delete_sesskey($this->get('user')->id); - } + if ($this->googleoauth->is_logged_in()) { + return false; + } else { + return $this->googleoauth->get_login_url(); } - - return google_authsub::login_url($CFG->wwwroot.'/portfolio/add.php?postcontrol=1&id=' . $this->exporter->get('id') . '&sesskey=' . sesskey(), google_picasa::REALM); } public function post_control($stage, $params) { @@ -74,44 +80,62 @@ class portfolio_plugin_picasa extends portfolio_plugin_push_base { return; } - if(!array_key_exists('token', $params)){ - throw new portfolio_plugin_exception('noauthtoken', 'portfolio_picasa'); + $this->initialize_oauth(); + if ($this->googleoauth->is_logged_in()) { + return false; + } else { + return $this->googleoauth->get_login_url(); } + } - // we now have our auth token, get a session token.. - $gauth = new google_authsub(false, $params['token']); - - $this->sessionkey = $gauth->get_sessiontoken(); - - google_picasa::set_sesskey($this->sessionkey, $this->get('user')->id); + public static function has_admin_config() { + return true; } public static function allows_multiple_instances() { return false; } -} -/** - * Registers to the user_deleted event to revoke any - * subauth tokens we have from them - * - * @param $user user object - * @return boolean true in all cases as its only minor cleanup - */ -function portfolio_picasa_user_deleted($user){ - // it is only by luck that the user prefstill exists now? - // We probably need a pre-delete event? - if($sesskey = google_picasa::get_sesskey($user->id)){ - try{ - $gauth = new google_authsub($sesskey); - - $gauth->revoke_session_token(); - }catch(Exception $e){ - // we don't care that much about success- just being good - // google api citzens - return true; - } + public static function get_allowed_config() { + return array('clientid', 'secret'); } - return true; + public function admin_config_form(&$mform) { + $a = new stdClass; + $a->docsurl = get_docs_url('Google_OAuth2_Setup'); + $a->callbackurl = google_oauth::callback_url()->out(false); + + $mform->addElement('static', null, '', get_string('oauthinfo', 'portfolio_picasa', $a)); + + $mform->addElement('text', 'clientid', get_string('clientid', 'portfolio_picasa')); + $mform->addElement('text', 'secret', get_string('secret', 'portfolio_picasa')); + + $strrequired = get_string('required'); + $mform->addRule('clientid', $strrequired, 'required', null, 'client'); + $mform->addRule('secret', $strrequired, 'required', null, 'client'); + } + + private function initialize_oauth() { + $returnurl = new moodle_url('/portfolio/add.php'); + $returnurl->param('postcontrol', 1); + $returnurl->param('id', $this->exporter->get('id')); + $returnurl->param('sesskey', sesskey()); + + $clientid = $this->get_config('clientid'); + $secret = $this->get_config('secret'); + + $this->googleoauth = new google_oauth($clientid, $secret, $returnurl->out(false), google_picasa::REALM); + } + + public function instance_sanity_check() { + $clientid = $this->get_config('clientid'); + $secret = $this->get_config('secret'); + + // If there is no oauth config (e.g. plugins upgraded from < 2.3 then + // there will be no config and this plugin should be disabled. + if (empty($clientid) or empty($secret)) { + return 'nooauthcredentials'; + } + return 0; + } } diff --git a/portfolio/picasa/version.php b/portfolio/picasa/version.php index 7906a251af3..b56c5e65b00 100644 --- a/portfolio/picasa/version.php +++ b/portfolio/picasa/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2011112900; // The current plugin version (Date: YYYYMMDDXX) -$plugin->requires = 2011112900; // Requires this Moodle version -$plugin->component = 'portfolio_picasa'; // Full name of the plugin (used for diagnostics) +$plugin->version = 2012053001; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2012051100; // Requires this Moodle version. +$plugin->component = 'portfolio_picasa'; // Full name of the plugin (used for diagnostics). $plugin->cron = 0; diff --git a/repository/googledocs/db/upgrade.php b/repository/googledocs/db/upgrade.php new file mode 100644 index 00000000000..893ed39dced --- /dev/null +++ b/repository/googledocs/db/upgrade.php @@ -0,0 +1,70 @@ +. + +/** + * @param int $oldversion the version we are upgrading from + * @return bool result + */ +function xmldb_repository_googledocs_upgrade($oldversion) { + global $CFG, $DB; + + $dbman = $DB->get_manager(); + + if ($oldversion < 2012051400) { + // Delete old user preferences containing authsub tokens. + $DB->delete_records('user_preferences', array('name' => 'google_authsub_sesskey')); + upgrade_plugin_savepoint(true, 2012051400, 'repository', 'googledocs'); + } + + if ($oldversion < 2012053000) { + require_once($CFG->dirroot.'/repository/lib.php'); + $existing = $DB->get_record('repository', array('type' => 'googledocs'), '*', IGNORE_MULTIPLE); + + if ($existing) { + $googledocsplugin = new repository_type('googledocs', array(), true); + $googledocsplugin->delete(); + repository_googledocs_admin_upgrade_notification(); + } + + upgrade_plugin_savepoint(true, 2012053000, 'repository', 'googledocs'); + } + + return true; +} + +function repository_googledocs_admin_upgrade_notification() { + $admins = get_admins(); + + if (empty($admins)) { + return; + } + $mainadmin = reset($admins); + + foreach ($admins as $admin) { + $message = new stdClass(); + $message->component = 'moodle'; + $message->name = 'notices'; + $message->userfrom = $mainadmin; + $message->userto = $admin; + $message->smallmessage = get_string('oauth2upgrade_message_small', 'repository_googledocs'); + $message->subject = get_string('oauth2upgrade_message_subject', 'repository_googledocs'); + $message->fullmessage = get_string('oauth2upgrade_message_content', 'repository_googledocs'); + $message->fullmessagehtml = get_string('oauth2upgrade_message_content', 'repository_googledocs'); + $message->fullmessageformat = FORMAT_PLAIN; + $message->notification = 1; + message_send($message); + } +} diff --git a/repository/googledocs/lang/en/repository_googledocs.php b/repository/googledocs/lang/en/repository_googledocs.php index 38f1436b27a..4f7faa7b324 100644 --- a/repository/googledocs/lang/en/repository_googledocs.php +++ b/repository/googledocs/lang/en/repository_googledocs.php @@ -1,5 +1,4 @@ To use the Google Docs repository you must be registered with Google. Instructions for registing your installation with Google are described in Moodle Docs. The redirect url should be set to:

{$a->callbackurl}

'; +$string['oauth2upgrade_message_subject'] = 'Important information regarding Google Docs repository plugin'; +$string['oauth2upgrade_message_content'] = 'As part of the upgrade to Moodle 2.3, the Google Docs repository plugin has been disabled due to changes in Googles API. To re-enable your plugin, you must add and reconfigure configure oauth credentials of this plugin.'; +$string['oauth2upgrade_message_small'] = 'The Google Docs repository plugin has been disabled until configured with OAuth2'; $string['pluginname'] = 'Google Docs'; -$string['configplugin'] = 'Configurate Google Docs plugin'; +$string['secret'] = 'Secret'; + diff --git a/repository/googledocs/lib.php b/repository/googledocs/lib.php index 36648bdb81e..6ccb33bc436 100644 --- a/repository/googledocs/lib.php +++ b/repository/googledocs/lib.php @@ -34,55 +34,40 @@ require_once($CFG->libdir.'/googleapi.php'); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class repository_googledocs extends repository { - private $subauthtoken = ''; + private $googleoauth = null; public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) { - global $USER; parent::__construct($repositoryid, $context, $options); - // TODO: I wish there was somewhere we could explicitly put this outside of constructor.. - $googletoken = optional_param('token', false, PARAM_RAW); - if($googletoken){ - $gauth = new google_authsub(false, $googletoken); // will throw exception if fails - google_docs::set_sesskey($gauth->get_sessiontoken(), $USER->id); - } + $returnurl = new moodle_url('/repository/repository_callback.php', + array('callback' => 'yes', 'repo_id' =>$this->id)); + + $clientid = get_config('googledocs', 'clientid'); + $secret = get_config('googledocs', 'secret'); + $this->googleoauth = new google_oauth($clientid, $secret, $returnurl->out(false), google_docs::REALM); + $this->check_login(); } public function check_login() { - global $USER; - - $sesskey = google_docs::get_sesskey($USER->id); - - if($sesskey){ - try{ - $gauth = new google_authsub($sesskey); - $this->subauthtoken = $sesskey; - return true; - }catch(Exception $e){ - // sesskey is not valid, delete store and re-auth - google_docs::delete_sesskey($USER->id); - } - } - - return false; + return $this->googleoauth->is_logged_in(); } - public function print_login($ajax = true){ - global $CFG; - if($ajax){ - $ret = array(); - $popup_btn = new stdClass(); - $popup_btn->type = 'popup'; - $returnurl = $CFG->wwwroot.'/repository/repository_callback.php?callback=yes&repo_id='.$this->id; - $popup_btn->url = google_authsub::login_url($returnurl, google_docs::REALM); - $ret['login'] = array($popup_btn); - return $ret; + public function print_login() { + $url = $this->googleoauth->get_login_url(); + + if ($this->options['ajax']) { + $popup = new stdClass(); + $popup->type = 'popup'; + $popup->url = $url->out(false); + return array('login' => array($popup)); + } else { + echo ''.get_string('login', 'repository').''; } } public function get_listing($path='', $page = '') { - $gdocs = new google_docs(new google_authsub($this->subauthtoken)); + $gdocs = new google_docs($this->googleoauth); $ret = array(); $ret['dynload'] = true; @@ -91,7 +76,7 @@ class repository_googledocs extends repository { } public function search($search_text, $page = 0) { - $gdocs = new google_docs(new google_authsub($this->subauthtoken)); + $gdocs = new google_docs($this->googleoauth); $ret = array(); $ret['dynload'] = true; @@ -99,37 +84,44 @@ class repository_googledocs extends repository { return $ret; } - public function logout(){ - global $USER; - - $token = google_docs::get_sesskey($USER->id); - - $gauth = new google_authsub($token); - // revoke token from google - $gauth->revoke_session_token(); - - google_docs::delete_sesskey($USER->id); - $this->subauthtoken = ''; - + public function logout() { + $this->googleoauth->log_out(); return parent::logout(); } public function get_file($url, $file = '') { - global $CFG; + $gdocs = new google_docs($this->googleoauth); + $path = $this->prepare_file($file); - - $fp = fopen($path, 'w'); - $gdocs = new google_docs(new google_authsub($this->subauthtoken)); - $gdocs->download_file($url, $fp); - - return array('path'=>$path, 'url'=>$url); + return $gdocs->download_file($url, $path); } public function supported_filetypes() { - return array('document'); + return '*'; } public function supported_returntypes() { return FILE_INTERNAL; } + + public static function get_type_option_names() { + return array('clientid', 'secret', 'pluginname'); + } + + public static function type_config_form($mform, $classname = 'repository') { + + $a = new stdClass; + $a->docsurl = get_docs_url('Google_OAuth2_Setup'); + $a->callbackurl = google_oauth::callback_url()->out(false); + + $mform->addElement('static', null, '', get_string('oauthinfo', 'repository_googledocs', $a)); + + parent::type_config_form($mform); + $mform->addElement('text', 'clientid', get_string('clientid', 'repository_googledocs')); + $mform->addElement('text', 'secret', get_string('secret', 'repository_googledocs')); + + $strrequired = get_string('required'); + $mform->addRule('clientid', $strrequired, 'required', null, 'client'); + $mform->addRule('secret', $strrequired, 'required', null, 'client'); + } } -//Icon from: http://www.iconspedia.com/icon/google-2706.html +// Icon from: http://www.iconspedia.com/icon/google-2706.html. diff --git a/repository/googledocs/version.php b/repository/googledocs/version.php index 79c6db7ff39..2e03f6c9c43 100644 --- a/repository/googledocs/version.php +++ b/repository/googledocs/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2011112900; // The current plugin version (Date: YYYYMMDDXX) -$plugin->requires = 2011112900; // Requires this Moodle version -$plugin->component = 'repository_googledocs'; // Full name of the plugin (used for diagnostics) +$plugin->version = 2012053000; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2012051100; // Requires this Moodle version. +$plugin->component = 'repository_googledocs'; // Full name of the plugin (used for diagnostics). diff --git a/repository/picasa/db/upgrade.php b/repository/picasa/db/upgrade.php new file mode 100644 index 00000000000..082e5e3433f --- /dev/null +++ b/repository/picasa/db/upgrade.php @@ -0,0 +1,70 @@ +. + +/** + * @param int $oldversion the version we are upgrading from + * @return bool result + */ +function xmldb_repository_picasa_upgrade($oldversion) { + global $CFG, $DB; + + $dbman = $DB->get_manager(); + + if ($oldversion < 2012051400) { + // Delete old user preferences storing authsub tokens. + $DB->delete_records('user_preferences', array('name' => 'google_authsub_sesskey_picasa')); + upgrade_plugin_savepoint(true, 2012051400, 'repository', 'picasa'); + } + + if ($oldversion < 2012053000) { + require_once($CFG->dirroot.'/repository/lib.php'); + $existing = $DB->get_record('repository', array('type' => 'picasa'), '*', IGNORE_MULTIPLE); + + if ($existing) { + $picasaplugin = new repository_type('picasa', array(), true); + $picasaplugin->delete(); + repository_picasa_admin_upgrade_notification(); + } + + upgrade_plugin_savepoint(true, 2012053000, 'repository', 'picasa'); + } + + return true; +} + +function repository_picasa_admin_upgrade_notification() { + $admins = get_admins(); + + if (empty($admins)) { + return; + } + $mainadmin = reset($admins); + + foreach ($admins as $admin) { + $message = new stdClass(); + $message->component = 'moodle'; + $message->name = 'notices'; + $message->userfrom = $mainadmin; + $message->userto = $admin; + $message->smallmessage = get_string('oauth2upgrade_message_small', 'repository_picasa'); + $message->subject = get_string('oauth2upgrade_message_subject', 'repository_picasa'); + $message->fullmessage = get_string('oauth2upgrade_message_content', 'repository_picasa'); + $message->fullmessagehtml = get_string('oauth2upgrade_message_content', 'repository_picasa'); + $message->fullmessageformat = FORMAT_PLAIN; + $message->notification = 1; + message_send($message); + } +} diff --git a/repository/picasa/lang/en/repository_picasa.php b/repository/picasa/lang/en/repository_picasa.php index 23d9dbe66bb..89d75a68079 100644 --- a/repository/picasa/lang/en/repository_picasa.php +++ b/repository/picasa/lang/en/repository_picasa.php @@ -1,5 +1,4 @@ To use the Picasa repository you must be registered with Google. Instructions for registing your installation with Google are described in Moodle Docs. The redirect url should be set to:

{$a->callbackurl}

'; +$string['oauth2upgrade_message_subject'] = 'Important information regarding Picasa repository plugin'; +$string['oauth2upgrade_message_content'] = 'As part of the upgrade to Moodle 2.3, the Picasa repository plugin has been disabled due to changes in Googles API. To re-enable your plugin, you must add and reconfigure configure oauth credentials of this plugin.'; +$string['oauth2upgrade_message_small'] = 'The Picasa repository plugin has been disabled until configured with OAuth2'; $string['picasa:view'] = 'View picasa repository'; $string['pluginname'] = 'Picasa web album'; -$string['configplugin'] = 'Picasa repository configuration'; +$string['secret'] = 'Secret'; diff --git a/repository/picasa/lib.php b/repository/picasa/lib.php index 6a83ca08238..902238025fa 100644 --- a/repository/picasa/lib.php +++ b/repository/picasa/lib.php @@ -36,58 +36,40 @@ require_once($CFG->libdir.'/googleapi.php'); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class repository_picasa extends repository { - private $subauthtoken = ''; + private $googleoauth = null; public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) { - global $USER; parent::__construct($repositoryid, $context, $options); - // TODO: I wish there was somewhere we could explicitly put this outside of constructor.. - $googletoken = optional_param('token', false, PARAM_RAW); - if($googletoken){ - $gauth = new google_authsub(false, $googletoken); // will throw exception if fails - google_picasa::set_sesskey($gauth->get_sessiontoken(), $USER->id); - } + $returnurl = new moodle_url('/repository/repository_callback.php', + array('callback' => 'yes', 'repo_id' =>$this->id)); + + $clientid = get_config('picasa', 'clientid'); + $secret = get_config('picasa', 'secret'); + $this->googleoauth = new google_oauth($clientid, $secret, $returnurl->out(false), google_picasa::REALM); + $this->check_login(); } public function check_login() { - global $USER; - - $sesskey = google_picasa::get_sesskey($USER->id); - - if($sesskey){ - try{ - $gauth = new google_authsub($sesskey); - $this->subauthtoken = $sesskey; - return true; - }catch(Exception $e){ - // sesskey is not valid, delete store and re-auth - google_picasa::delete_sesskey($USER->id); - } - } - - return false; + return $this->googleoauth->is_logged_in(); } - public function print_login(){ - global $CFG; - $returnurl = $CFG->wwwroot.'/repository/repository_callback.php?callback=yes&repo_id='.$this->id; - $authurl = google_authsub::login_url($returnurl, google_picasa::REALM); - if($this->options['ajax']){ - $ret = array(); - $popup_btn = new stdClass(); - $popup_btn->type = 'popup'; - $popup_btn->url = $authurl; - $ret['login'] = array($popup_btn); - return $ret; + public function print_login() { + $url = $this->googleoauth->get_login_url(); + + if ($this->options['ajax']) { + $popup = new stdClass(); + $popup->type = 'popup'; + $popup->url = $url->out(false); + return array('login' => array($popup)); } else { - echo 'Login'; + echo ''.get_string('login', 'repository').''; } } public function get_listing($path='', $page = '') { - $picasa = new google_picasa(new google_authsub($this->subauthtoken)); + $picasa = new google_picasa($this->googleoauth); $ret = array(); $ret['dynload'] = true; @@ -101,7 +83,7 @@ class repository_picasa extends repository { } public function search($search_text, $page = 0) { - $picasa = new google_picasa(new google_authsub($this->subauthtoken)); + $picasa = new google_picasa($this->googleoauth); $ret = array(); $ret['manage'] = google_picasa::MANAGE_URL; @@ -109,22 +91,12 @@ class repository_picasa extends repository { return $ret; } - public function logout(){ - global $USER; - - $token = google_picasa::get_sesskey($USER->id); - - $gauth = new google_authsub($token); - // revoke token from google - $gauth->revoke_session_token(); - - google_picasa::delete_sesskey($USER->id); - $this->subauthtoken = ''; - + public function logout() { + $this->googleoauth->log_out(); return parent::logout(); } - public function get_name(){ + public function get_name() { return get_string('pluginname', 'repository_picasa'); } public function supported_filetypes() { @@ -133,7 +105,27 @@ class repository_picasa extends repository { public function supported_returntypes() { return (FILE_INTERNAL | FILE_EXTERNAL); } + + public static function get_type_option_names() { + return array('clientid', 'secret', 'pluginname'); + } + + public static function type_config_form($mform, $classname = 'repository') { + $a = new stdClass; + $a->docsurl = get_docs_url('Google_OAuth2_Setup'); + $a->callbackurl = google_oauth::callback_url()->out(false); + + $mform->addElement('static', null, '', get_string('oauthinfo', 'repository_picasa', $a)); + + parent::type_config_form($mform); + $mform->addElement('text', 'clientid', get_string('clientid', 'repository_picasa')); + $mform->addElement('text', 'secret', get_string('secret', 'repository_picasa')); + + $strrequired = get_string('required'); + $mform->addRule('clientid', $strrequired, 'required', null, 'client'); + $mform->addRule('secret', $strrequired, 'required', null, 'client'); + } } // Icon for this plugin retrieved from http://www.iconspedia.com/icon/picasa-2711.html -// Where the license is said documented to be Free +// Where the license is said documented to be Free. diff --git a/repository/picasa/version.php b/repository/picasa/version.php index 5ee9d18b00a..c6dc0fc84ff 100644 --- a/repository/picasa/version.php +++ b/repository/picasa/version.php @@ -26,6 +26,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2011112900; // The current plugin version (Date: YYYYMMDDXX) -$plugin->requires = 2011112900; // Requires this Moodle version -$plugin->component = 'repository_picasa'; // Full name of the plugin (used for diagnostics) +$plugin->version = 2012053000; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2012051100; // Requires this Moodle version. +$plugin->component = 'repository_picasa'; // Full name of the plugin (used for diagnostics).