Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

OAuth 2 API: Difference between revisions

From MoodleDocs
(ja link)
 
(10 intermediate revisions by 4 users not shown)
Line 15: Line 15:
=== Open ID Connect ===
=== Open ID Connect ===
Open ID Connect is a protocol built on top of OAuth 2 which provides some standardisation and inter-operability for OAuth 2 based services. If a "base service url" is entered for an Issuer - Moodle will attempt to retrieve the "well known configuration" which provides all the information about the other endpoints required to complete the setup for this service. E.g. for Google - the base service url is "https://accounts.google.com/". By appending ".well-known/openid-configuration" to the url we can find the service description at https://accounts.google.com/.well-known/openid-configuration which contains all the required information for us to automatically complete the setup for this service. This will work with any Open ID connect compliant service.
Open ID Connect is a protocol built on top of OAuth 2 which provides some standardisation and inter-operability for OAuth 2 based services. If a "base service url" is entered for an Issuer - Moodle will attempt to retrieve the "well known configuration" which provides all the information about the other endpoints required to complete the setup for this service. E.g. for Google - the base service url is "https://accounts.google.com/". By appending ".well-known/openid-configuration" to the url we can find the service description at https://accounts.google.com/.well-known/openid-configuration which contains all the required information for us to automatically complete the setup for this service. This will work with any Open ID connect compliant service.
=== Open Badge Connect API ===
Open Badge Connect API is a specification built by IMS Global which provides some standardisation and interoperability for OAuth 2 based services. If a "base service URL" is entered for an Issuer, Moodle will attempt to retrieve the "well-known configuration" (.well-known/badgeconnect.json) which provides all the information about the endpoints required to complete the setup for this service. Besides, if no client id and secret are given, the registration endpoint will be called to get them (and safe time and effort to the admin).
E.g. for IMS Global testing platform, the base service URL is "https://dc.imsglobal.org/". By appending ".well-known/badgeconnect.json" to the URL we can find the service description which contains all the required information for us to automatically complete the setup for this service (endpoints, client id and secret, image...).


=== User field mappings ===
=== User field mappings ===
Line 20: Line 24:




== I setup an OAuth 2 Issuer - how to I use it in code? ==
== How a new issuer service should be implemented? ==
Any OAuth2 Service should have a class into the lib/classes/oauth2/service/ folder extending from a discovery system (core\oauth2\discovery\base_definition) and implementing core\oauth2\service\issuer_interface:
 
* Discovery system indicates the way the endpoints should be obtained.
* Issuer interface has several common methods, called from lib/classes/oauth2/api.php to initialise or discover endpoints.
 
<syntaxhighlight lang="php">class google extends openidconnect implements issuer_interface {
[...]
 
class imsobv2p1 extends imsbadgeconnect implements issuer_interface {
[...]
 
</syntaxhighlight>
 
 
== I set up an OAuth 2 Issuer - how do I use it in code? ==
Any plugin that wants to use the configuration information provided by an OAuth issuer first needs to determine which issuer they should use for authentication. This is typically done by adding a setting that lets the admin choose from the list of configured issuers. An example is the "Google Drive" repository. It's possible to have multiple, very similar OAuth Issuers configured e.g. One public Google Issuer and one that is restricted to a specific domain. Once we know the Issuer we wish to use we can use it's ID to get an \core\oauth2\client class which will let us make requests against that API.
Any plugin that wants to use the configuration information provided by an OAuth issuer first needs to determine which issuer they should use for authentication. This is typically done by adding a setting that lets the admin choose from the list of configured issuers. An example is the "Google Drive" repository. It's possible to have multiple, very similar OAuth Issuers configured e.g. One public Google Issuer and one that is restricted to a specific domain. Once we know the Issuer we wish to use we can use it's ID to get an \core\oauth2\client class which will let us make requests against that API.


<code php>
<syntaxhighlight lang="php">// Get an issuer from the id.
 
// Get an issuer from the id.
$issuer = \core\oauth2\api::get_issuer($issuerid);
$issuer = \core\oauth2\api::get_issuer($issuerid);
// Get an OAuth client from the issuer.
// Get an OAuth client from the issuer.
$client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, $scopes);                           
$client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, $scopes);                           


</code>
</syntaxhighlight>
 
Despite what is said in the code documentation, it is best to check that the client is authenticated and if not,  redirect the user to log in.
 
<syntaxhighlight lang="php">
if (!$client->is_logged_in()) {
    redirect($client->get_login_url());
}
</syntaxhighlight>  


What is return URL?  
'''What is return URL?'''


This is a url that will take you back to the current page. The way OAuth works is that the call to get the OAuth client will check to see if we have a valid session with all of the required scopes. If we don't - the user will be redirected immediately to the OAuth login page. When they have logged in - they are redirected back to the "returnurl" in Moodle which must end up making the same call to get an OAuth client - except this time we have a valid session so the client is returned. The returnurl MUST include the sesskey as one of the parameters.
This is a url that will take you back to the current page. The way OAuth works is that the call to get the OAuth client will check to see if we have a valid session with all of the required scopes. If we don't - the user will be redirected immediately to the OAuth login page. When they have logged in - they are redirected back to the "returnurl" in Moodle which must end up making the same call to get an OAuth client - except this time we have a valid session so the client is returned. The returnurl MUST include the sesskey as one of the parameters.


What are the scopes?
'''What are the scopes?'''


These are named "permission grants" which the user is asked to accept. E.g. "email" is a scope which allows Moodle to see the email address for your logged in account. Each of the requested permissions will be displayed to the user with a description of what they mean and the user will have to "grant" Moodle this level of access - or cancel the operation. It is best practice not to request more scopes than are needed at any one time, and to incrementally request additional scopes as they are going to be used by different features in Moodle. For example - when logging into Moodle we only need to request access to the users basic profile and email address. When accessing a repository - we will need to request read/write access to the users files. Each time we create an oauth client class, we list the scopes that we will be using with this instance of the oauth client - if the user has only approved some of the scopes we request - they will be redirected to a consent screen where they approve the additional level of access.
These are named "permission grants" which the user is asked to accept. E.g. "email" is a scope which allows Moodle to see the email address for your logged in account. Each of the requested permissions will be displayed to the user with a description of what they mean and the user will have to "grant" Moodle this level of access - or cancel the operation. It is best practice not to request more scopes than are needed at any one time, and to incrementally request additional scopes as they are going to be used by different features in Moodle. For example - when logging into Moodle we only need to request access to the users basic profile and email address. When accessing a repository - we will need to request read/write access to the users files. Each time we create an oauth client class, we list the scopes that we will be using with this instance of the oauth client - if the user has only approved some of the scopes we request - they will be redirected to a consent screen where they approve the additional level of access.
So - what can I do with my oauth client ?
Make requests! This class extends the moodle curl class but includes authentication information with each request automatically. This means you can use standard functions like $client->get() or $client->post() to manually call rest api functions. This class will also honour the configured Moodle security settings like ipaddress black lists and proxy settings.
How do I make it easier to call API functions?
There is an abstract class \core\oauth2\rest which contains helper functions to allow you to wrap a external rest api in an easier to use class. To use it, make a subclass in your own plugin and define the "get_api_functions()" method.
<syntaxhighlight lang="php">/**                                                                                                                               
* Google Drive Rest API.                                                                                                         
*                                                                                                                                 
* @package    fileconverter_googledrive                                                                                           
* @copyright  2017 Damyon Wiese                                                                                                   
* @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later                                                           
*/                                                                                                                               
namespace fileconverter_googledrive;                                                                                               
                                                                                                                                   
defined('MOODLE_INTERNAL') || die();                                                                                               
                                                                                                                                   
/**                                                                                                                               
* Google Drive Rest API.                                                                                                         
*                                                                                                                                 
* @copyright  2017 Damyon Wiese                                                                                                   
* @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later                                                           
*/                                                                                                                               
class rest extends \core\oauth2\rest {                                                                                             
                                                                                                                                   
    /**                                                                                                                           
    * Define the functions of the rest API.                                                                                       
    *                                                                                                                             
    * @return array Example:                                                                                                     
    *  [ 'listFiles' => [ 'method' => 'get', 'endpoint' => 'http://...', 'args' => [ 'folder' => PARAM_STRING ] ] ]               
    */                                                                                                                           
    public function get_api_functions() {                                                                                         
        return [                                                                                                                   
            'create' => [                                                                                                         
                'endpoint' => 'https://www.googleapis.com/drive/v3/files',                                                         
                'method' => 'post',                                                                                               
                'args' => [                                                                                                       
                    'fields' => PARAM_RAW                                                                                         
                ],                                                                                                                 
                'response' => 'json'                                                                                               
            ],                                                                                                                     
            'delete' => [                                                                                                         
                'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}',                                               
                'method' => 'delete',                                                                                             
                'args' => [                                                                                                       
                    'fileid' => PARAM_RAW                                                                                         
                ],                                                                                                                 
                'response' => 'json'                                                                                               
            ],                                                                                                                     
        ];                                                                                                                         
    }                                                                                                                             
}             
</syntaxhighlight>
This example defines 2 functions in the external API 'create' and 'delete'. It specifies the http methods to use when calling these functions as well as the list of parameters. It also specifies that these functions return 'json' which means that the rest class will automatically decode the json response into an object. The url for each function call can contain parameters in the url (marked with curly braces). These will be replaced when they are passed as arguments to the function. Any remaining arguments will be appended as query parameters.
To use this class - we pass the oauth2 client to the constructor and then use the "call" method to call functions from the api.
<syntaxhighlight lang="php">
$service = new \fileconverter_googledrive\rest($client);         
$params = ['fileid' => $fileid];                                                                                                                         
$service->call('delete', $params);                       
</syntaxhighlight>
== How do I call API functions when the user is not logged in (e.g. from a scheduled task)? ==
Moodle allows you to connect a "system account" to any of the OAuth issuers. This is optional - so before enabling functionality that relies on this level of access you should check that the system account has been connected:
<syntaxhighlight lang="php">
if ($issuer->is_system_account_connected()) {
    // Rock and roll.
}
</syntaxhighlight>
If a system account has been connected - we can use it to get an authenticated oauth client (no redirects involved).
<syntaxhighlight lang="php">
$client = \core\oauth2\api::get_system_oauth_client($issuer);
</syntaxhighlight>
This client can now be used to access apis as the Moodle system user.
If your code is going to use additional login scopes with this API - it must list all of the scopes it will use in a callback function so that the Moodle administrator can consent and agree to all the scopes when they connect the system account.
<syntaxhighlight lang="php">
**                                                                                                                               
* Callback to get the required scopes for system account.                                                                         
*                                                                                                                                 
* @param \core\oauth2\issuer $issuer                                                                                             
* @return string                                                                                                                 
*/                                                                                                                               
function fileconverter_googledrive_oauth2_system_scopes(\core\oauth2\issuer $issuer) {                                             
    if ($issuer->get('id') == get_config('fileconverter_googledrive', 'issuerid')) {                                               
        return 'https://www.googleapis.com/auth/drive';                                                                           
    }                                                                                                                             
    return '';                                                                                                                     
</syntaxhighlight>
The way the system account works is that a Moodle admin "connects" the system account by logging in with this account and accepting the permissions from the "Site administration -> Server -> OAuth 2 Services" page. One of the permissions they must accept is for offline access. This means that Moodle will receive a refresh token and can store it against that issuer. When we need to get a logged in OAuth client - we can exchange the refresh token for an access token directly, without having to login. The refresh token is updated by a scheduled task to make sure it never expires.
[[ja:開発:OAuth_2_API]]

Latest revision as of 15:04, 22 March 2023

OAuth 2 API

Moodle 3.3


The OAuth 2 API is a set of classes that provide OAuth 2 functionality for integrating with remote systems. They exist in the folder /lib/classes/oauth2/ and there are a few concepts to be aware of.

Issuers

An OAuth Issuer is a named external system that provides identity and API access by issuing OAuth access tokens. They are configured manually at "Site administration -> Server -> OAuth 2 Services" and common ones can be quickly created from a template (Google, Office 365 and Facebook). An Issuer has a name and icon (for display on the login page), a Client ID and Client Secret (part of the OAuth spec).

Endpoints

An OAuth issuer must have a number of endpoints defined which are the URL's used to fetch and exchange access tokens, as well as fetch identity information. These will be setup automatically for OAuth services created from a template, or OAuth services using Open ID Connect.

The 3 standard endpoints which must be defined are the "authorization endpoint", "token endpoint" and "userinfo endpoint" - these are 3 urls which are used by the OAuth protocol to "allow the user to login", "obtain tokens to access the api" and "get the logged in user information".

Open ID Connect

Open ID Connect is a protocol built on top of OAuth 2 which provides some standardisation and inter-operability for OAuth 2 based services. If a "base service url" is entered for an Issuer - Moodle will attempt to retrieve the "well known configuration" which provides all the information about the other endpoints required to complete the setup for this service. E.g. for Google - the base service url is "https://accounts.google.com/". By appending ".well-known/openid-configuration" to the url we can find the service description at https://accounts.google.com/.well-known/openid-configuration which contains all the required information for us to automatically complete the setup for this service. This will work with any Open ID connect compliant service.

Open Badge Connect API

Open Badge Connect API is a specification built by IMS Global which provides some standardisation and interoperability for OAuth 2 based services. If a "base service URL" is entered for an Issuer, Moodle will attempt to retrieve the "well-known configuration" (.well-known/badgeconnect.json) which provides all the information about the endpoints required to complete the setup for this service. Besides, if no client id and secret are given, the registration endpoint will be called to get them (and safe time and effort to the admin). E.g. for IMS Global testing platform, the base service URL is "https://dc.imsglobal.org/". By appending ".well-known/badgeconnect.json" to the URL we can find the service description which contains all the required information for us to automatically complete the setup for this service (endpoints, client id and secret, image...).

User field mappings

The other information we need to know about an OAuth 2 service is how to map the user information into Moodle user fields. We do this by adding to the list of user field mappings for the Issuer. The mappings for Open ID Connect services are standard and will be automatically created when setting up an Open ID compliant service - for other services you will need to create the mappings manually. Moodle will use this information to import the user profile fields when creating new accounts. The most important user field mappings are the username and email which are used to identify the Moodle account associated with the OAuth 2 login.


How a new issuer service should be implemented?

Any OAuth2 Service should have a class into the lib/classes/oauth2/service/ folder extending from a discovery system (core\oauth2\discovery\base_definition) and implementing core\oauth2\service\issuer_interface:

  • Discovery system indicates the way the endpoints should be obtained.
  • Issuer interface has several common methods, called from lib/classes/oauth2/api.php to initialise or discover endpoints.
class google extends openidconnect implements issuer_interface {
[...]

class imsobv2p1 extends imsbadgeconnect implements issuer_interface {
[...]


I set up an OAuth 2 Issuer - how do I use it in code?

Any plugin that wants to use the configuration information provided by an OAuth issuer first needs to determine which issuer they should use for authentication. This is typically done by adding a setting that lets the admin choose from the list of configured issuers. An example is the "Google Drive" repository. It's possible to have multiple, very similar OAuth Issuers configured e.g. One public Google Issuer and one that is restricted to a specific domain. Once we know the Issuer we wish to use we can use it's ID to get an \core\oauth2\client class which will let us make requests against that API.

// Get an issuer from the id.
$issuer = \core\oauth2\api::get_issuer($issuerid);
// Get an OAuth client from the issuer.
$client = \core\oauth2\api::get_user_oauth_client($this->issuer, $returnurl, $scopes);

Despite what is said in the code documentation, it is best to check that the client is authenticated and if not, redirect the user to log in.

if (!$client->is_logged_in()) {
    redirect($client->get_login_url());
}

What is return URL?

This is a url that will take you back to the current page. The way OAuth works is that the call to get the OAuth client will check to see if we have a valid session with all of the required scopes. If we don't - the user will be redirected immediately to the OAuth login page. When they have logged in - they are redirected back to the "returnurl" in Moodle which must end up making the same call to get an OAuth client - except this time we have a valid session so the client is returned. The returnurl MUST include the sesskey as one of the parameters.

What are the scopes?

These are named "permission grants" which the user is asked to accept. E.g. "email" is a scope which allows Moodle to see the email address for your logged in account. Each of the requested permissions will be displayed to the user with a description of what they mean and the user will have to "grant" Moodle this level of access - or cancel the operation. It is best practice not to request more scopes than are needed at any one time, and to incrementally request additional scopes as they are going to be used by different features in Moodle. For example - when logging into Moodle we only need to request access to the users basic profile and email address. When accessing a repository - we will need to request read/write access to the users files. Each time we create an oauth client class, we list the scopes that we will be using with this instance of the oauth client - if the user has only approved some of the scopes we request - they will be redirected to a consent screen where they approve the additional level of access.

So - what can I do with my oauth client ?

Make requests! This class extends the moodle curl class but includes authentication information with each request automatically. This means you can use standard functions like $client->get() or $client->post() to manually call rest api functions. This class will also honour the configured Moodle security settings like ipaddress black lists and proxy settings.

How do I make it easier to call API functions?

There is an abstract class \core\oauth2\rest which contains helper functions to allow you to wrap a external rest api in an easier to use class. To use it, make a subclass in your own plugin and define the "get_api_functions()" method.

/**                                                                                                                                 
 * Google Drive Rest API.                                                                                                           
 *                                                                                                                                  
 * @package    fileconverter_googledrive                                                                                            
 * @copyright  2017 Damyon Wiese                                                                                                    
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later                                                             
 */                                                                                                                                 
namespace fileconverter_googledrive;                                                                                                
                                                                                                                                    
defined('MOODLE_INTERNAL') || die();                                                                                                
                                                                                                                                    
/**                                                                                                                                 
 * Google Drive Rest API.                                                                                                           
 *                                                                                                                                  
 * @copyright  2017 Damyon Wiese                                                                                                    
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later                                                             
 */                                                                                                                                 
class rest extends \core\oauth2\rest {                                                                                              
                                                                                                                                    
    /**                                                                                                                             
     * Define the functions of the rest API.                                                                                        
     *                                                                                                                              
     * @return array Example:                                                                                                       
     *  [ 'listFiles' => [ 'method' => 'get', 'endpoint' => 'http://...', 'args' => [ 'folder' => PARAM_STRING ] ] ]                
     */                                                                                                                             
    public function get_api_functions() {                                                                                           
        return [                                                                                                                    
            'create' => [                                                                                                           
                'endpoint' => 'https://www.googleapis.com/drive/v3/files',                                                          
                'method' => 'post',                                                                                                 
                'args' => [                                                                                                         
                    'fields' => PARAM_RAW                                                                                           
                ],                                                                                                                  
                'response' => 'json'                                                                                                
            ],                                                                                                                      
            'delete' => [                                                                                                           
                'endpoint' => 'https://www.googleapis.com/drive/v3/files/{fileid}',                                                 
                'method' => 'delete',                                                                                               
                'args' => [                                                                                                         
                    'fileid' => PARAM_RAW                                                                                           
                ],                                                                                                                  
                'response' => 'json'                                                                                                
            ],                                                                                                                      
        ];                                                                                                                          
    }                                                                                                                               
}

This example defines 2 functions in the external API 'create' and 'delete'. It specifies the http methods to use when calling these functions as well as the list of parameters. It also specifies that these functions return 'json' which means that the rest class will automatically decode the json response into an object. The url for each function call can contain parameters in the url (marked with curly braces). These will be replaced when they are passed as arguments to the function. Any remaining arguments will be appended as query parameters.

To use this class - we pass the oauth2 client to the constructor and then use the "call" method to call functions from the api.

$service = new \fileconverter_googledrive\rest($client);           

$params = ['fileid' => $fileid];                                                                                                                          

$service->call('delete', $params);

How do I call API functions when the user is not logged in (e.g. from a scheduled task)?

Moodle allows you to connect a "system account" to any of the OAuth issuers. This is optional - so before enabling functionality that relies on this level of access you should check that the system account has been connected:

if ($issuer->is_system_account_connected()) {
    // Rock and roll.
}

If a system account has been connected - we can use it to get an authenticated oauth client (no redirects involved).

$client = \core\oauth2\api::get_system_oauth_client($issuer);

This client can now be used to access apis as the Moodle system user.

If your code is going to use additional login scopes with this API - it must list all of the scopes it will use in a callback function so that the Moodle administrator can consent and agree to all the scopes when they connect the system account.

**                                                                                                                                 
 * Callback to get the required scopes for system account.                                                                          
 *                                                                                                                                  
 * @param \core\oauth2\issuer $issuer                                                                                               
 * @return string                                                                                                                   
 */                                                                                                                                 
function fileconverter_googledrive_oauth2_system_scopes(\core\oauth2\issuer $issuer) {                                              
    if ($issuer->get('id') == get_config('fileconverter_googledrive', 'issuerid')) {                                                
        return 'https://www.googleapis.com/auth/drive';                                                                             
    }                                                                                                                               
    return '';                                                                                                                      
}

The way the system account works is that a Moodle admin "connects" the system account by logging in with this account and accepting the permissions from the "Site administration -> Server -> OAuth 2 Services" page. One of the permissions they must accept is for offline access. This means that Moodle will receive a refresh token and can store it against that issuer. When we need to get a logged in OAuth client - we can exchange the refresh token for an access token directly, without having to login. The refresh token is updated by a scheduled task to make sure it never expires.