From f11a7d6a053c88c658121c63b401af70428bf0e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Dagef=C3=B6rde?= Date: Fri, 19 Oct 2018 19:44:02 +0200 Subject: [PATCH] MDL-63696 oauth2: Store system account access tokens in DB --- lib/classes/oauth2/access_token.php | 71 +++++++++++++++++++++++++++++ lib/classes/oauth2/client.php | 52 ++++++++++++++++++++- lib/db/install.xml | 16 +++++++ lib/db/upgrade.php | 27 +++++++++++ lib/oauthlib.php | 2 +- version.php | 2 +- 6 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 lib/classes/oauth2/access_token.php diff --git a/lib/classes/oauth2/access_token.php b/lib/classes/oauth2/access_token.php new file mode 100644 index 00000000000..fad48f39484 --- /dev/null +++ b/lib/classes/oauth2/access_token.php @@ -0,0 +1,71 @@ +. + +/** + * Loads/stores oauth2 access tokens in DB for system accounts in order to use a single token across multiple sessions. + * + * @package core + * @copyright 2018 Jan Dageförde + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\oauth2; + +defined('MOODLE_INTERNAL') || die(); + +use core\persistent; + +/** + * Loads/stores oauth2 access tokens in DB for system accounts in order to use a single token across multiple sessions. + * + * When a system user is authenticated via OAuth, we need to use a single access token across user sessions, + * because we want to avoid using multiple tokens at the same time for a single remote user. Reasons are that, + * first, redeeming the refresh token for an access token requires an additional request, and second, there is + * no guarantee that redeeming the refresh token doesn't invalidate *all* corresponding previous access tokes. + * As a result, we would need to either continuously request lots and lots of new access tokens, or persist the + * access token in the DB where it can be used from all sessions. Let's do the latter! + * + * @copyright 2018 Jan Dageförde + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class access_token extends persistent { + + /** The table name. */ + const TABLE = 'oauth2_access_token'; + + /** + * Return the definition of the properties of this model. + * + * @return array + */ + protected static function define_properties() { + return array( + // Issuer id instead of the system account id because, at the time of storing/loading a token we may not + // know the system account id. + 'issuerid' => array( + 'type' => PARAM_INT + ), + 'token' => array( + 'type' => PARAM_RAW, + ), + 'expires' => array( + 'type' => PARAM_INT, + ), + 'scope' => array( + 'type' => PARAM_RAW, + ), + ); + } +} diff --git a/lib/classes/oauth2/client.php b/lib/classes/oauth2/client.php index 7f3cc96f93d..bd27f8e8ada 100644 --- a/lib/classes/oauth2/client.php +++ b/lib/classes/oauth2/client.php @@ -33,7 +33,7 @@ use moodle_exception; use stdClass; /** - * Configurable oauth2 client class where the urls come from DB. + * Configurable oauth2 client class. URLs come from DB and access tokens from either DB (system accounts) or session (users'). * * @copyright 2017 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -152,6 +152,56 @@ class client extends \oauth2_client { return $name; } + /** + * Store a token between requests. Uses session named by get_tokenname for user account tokens + * and a database record for system account tokens. + * + * @param stdClass|null $token token object to store or null to clear + */ + protected function store_token($token) { + if (!$this->system) { + parent::store_token($token); + return; + } + + $this->accesstoken = $token; + + // Create or update a DB record with the new token. + $persistedtoken = access_token::get_record(['issuerid' => $this->issuer->get('id')]); + if ($token !== null) { + if (!$persistedtoken) { + $persistedtoken = new access_token(); + $persistedtoken->set('issuerid', $this->issuer->get('id')); + } + // Update values from $token. Don't use from_record because that would skip validation. + $persistedtoken->set('token', $token->token); + $persistedtoken->set('expires', $token->expires); + $persistedtoken->set('scope', $token->scope); + $persistedtoken->save(); + } else { + if ($persistedtoken) { + $persistedtoken->delete(); + } + } + } + + /** + * Retrieve a stored token from session (user accounts) or database (system accounts). + * + * @return stdClass|null token object + */ + protected function get_stored_token() { + if ($this->system) { + $token = access_token::get_record(['issuerid' => $this->issuer->get('id')]); + if ($token !== false) { + return $token->to_record(); + } + return null; + } + + return parent::get_stored_token(); + } + /** * Get a list of the mapping user fields in an associative array. * diff --git a/lib/db/install.xml b/lib/db/install.xml index 7fd383d97c8..51c65688d25 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -3926,6 +3926,22 @@ + + + + + + + + + + + + + + + +
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 9b9fb47a21e..d82caec95b0 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2771,5 +2771,32 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2018110700.01); } + if ($oldversion < 2018111300.01) { + // Define table oauth2_access_token to be created. + $table = new xmldb_table('oauth2_access_token'); + + // Adding fields to table oauth2_access_token. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('issuerid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('token', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null); + $table->add_field('expires', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('scope', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null); + + // Adding keys to table oauth2_access_token. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + $table->add_key('issueridkey', XMLDB_KEY_FOREIGN_UNIQUE, ['issuerid'], 'oauth2_issuer', ['id']); + + // Conditionally launch create table for oauth2_access_token. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2018111300.01); + } + return true; } diff --git a/lib/oauthlib.php b/lib/oauthlib.php index c933decedbf..a20d2d971c5 100644 --- a/lib/oauthlib.php +++ b/lib/oauthlib.php @@ -396,7 +396,7 @@ abstract class oauth2_client extends curl { /** @var string $scope of the authentication request */ protected $scope = ''; /** @var stdClass $accesstoken access token object */ - private $accesstoken = null; + protected $accesstoken = null; /** @var string $refreshtoken refresh token string */ protected $refreshtoken = ''; /** @var string $mocknextresponse string */ diff --git a/version.php b/version.php index 6a8ab5b3d7b..d60bf4f7f7b 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2018111300.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2018111300.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.