MDL-63696 oauth2: Store system account access tokens in DB

This commit is contained in:
Jan Dageförde 2018-10-19 19:44:02 +02:00
parent dccda6546b
commit f11a7d6a05
No known key found for this signature in database
GPG Key ID: 2239CFA64B5E4FCC
6 changed files with 167 additions and 3 deletions

View File

@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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 3 of the License, or
// (at your option) any later version.
//
// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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 <jan.dagefoerde@ercis.uni-muenster.de>
* @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 <jan.dagefoerde@ercis.uni-muenster.de>
* @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,
),
);
}
}

View File

@ -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.
*

View File

@ -3926,6 +3926,22 @@
<INDEX NAME="predictionidanduseridandactionname" UNIQUE="false" FIELDS="predictionid, userid, actionname"/>
</INDEXES>
</TABLE>
<TABLE NAME="oauth2_access_token" COMMENT="Stores access tokens for system accounts in order to be able to use a single token across multiple sessions">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time this record was created."/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time this record was modified."/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The user who modified this record."/>
<FIELD NAME="issuerid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Corresponding oauth2 issuer"/>
<FIELD NAME="token" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="access token"/>
<FIELD NAME="expires" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Expiry timestamp (according to the issuer)"/>
<FIELD NAME="scope" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="issueridkey" TYPE="foreign-unique" FIELDS="issuerid" REFTABLE="oauth2_issuer" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="analytics_used_analysables" COMMENT="List of analysables used by each model">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>

View File

@ -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;
}

View File

@ -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 */

View File

@ -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.