MDL-33507 - oauth2lib: switch 'code' to oauth2code
[moodle.git] / lib / oauthlib.php
index 36ee14e..587ba19 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
+
+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 <talktodan@gmail.com>
+ * @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 moodle_url URL to return to after authenticating */
+    private $returnurl = null;
+    /** 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 moodle_url $returnurl
+     * @param string $scope
+     */
+    public function __construct($clientid, $clientsecret, moodle_url $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('oauth2code', 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->out_as_local_url(false),
+                              '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;
+    }
+}