From f9f243f93e023340efbc0e9d018ccbf340e471a8 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Tue, 28 Feb 2017 12:12:14 +0800 Subject: [PATCH] MDL-58090 oauth2: Complete phpdocs Part of MDL-58220 --- admin/tool/oauth2/userfieldmappings.php | 2 +- auth/oauth2/auth.php | 3 +- auth/oauth2/classes/auth.php | 10 +- auth/oauth2/lang/en/auth_oauth2.php | 4 +- auth/oauth2/login.php | 2 +- lib/classes/oauth2/api.php | 158 ++++++++++++++++++++++ lib/classes/oauth2/client.php | 11 ++ lib/classes/oauth2/endpoint.php | 7 + lib/classes/oauth2/issuer.php | 17 +++ lib/classes/oauth2/user_field_mapping.php | 5 + lib/classes/plugin_manager.php | 4 +- 11 files changed, 215 insertions(+), 8 deletions(-) diff --git a/admin/tool/oauth2/userfieldmappings.php b/admin/tool/oauth2/userfieldmappings.php index 1bf277d9069..d0961ceb9d8 100644 --- a/admin/tool/oauth2/userfieldmappings.php +++ b/admin/tool/oauth2/userfieldmappings.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * OAuth 2 Endpoing Configuration page. + * OAuth 2 Endpoint Configuration page. * * @package tool_oauth2 * @copyright 2017 Damyon Wiese diff --git a/auth/oauth2/auth.php b/auth/oauth2/auth.php index 0d5ecd7f79a..601dd4b2c4d 100644 --- a/auth/oauth2/auth.php +++ b/auth/oauth2/auth.php @@ -27,7 +27,8 @@ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/authlib.php'); /** - * Plugin for oauth2 authentication. + * Plugin for oauth2 authentication. This is a way to use namespaces even though + * moodle expects a non-namespaced file here. * * @package auth_oauth2 * @copyright 2017 Damyon Wiese diff --git a/auth/oauth2/classes/auth.php b/auth/oauth2/classes/auth.php index 545a162f2dd..f188e439a8c 100644 --- a/auth/oauth2/classes/auth.php +++ b/auth/oauth2/classes/auth.php @@ -179,7 +179,12 @@ class auth extends \auth_plugin_base { return false; } - private function is_ready_for_login_page($issuer) { + /** + * Do some checks on the identity provider before showing it on the login page. + * @param core\oauth2\issuer + * @return boolean + */ + private function is_ready_for_login_page(\core\oauth2\issuer $issuer) { return !empty($issuer->get('clientid')) && !empty($issuer->get('clientsecret')) && $issuer->is_authentication_supported() && @@ -188,6 +193,9 @@ class auth extends \auth_plugin_base { /** * Return a list of identity providers to display on the login page. + * + * @param string|moodle_url $wantsurl The requested URL. + * @return array (containing url, iconurl and name). */ public function loginpage_idp_list($wantsurl) { $providers = \core\oauth2\api::get_all_issuers(); diff --git a/auth/oauth2/lang/en/auth_oauth2.php b/auth/oauth2/lang/en/auth_oauth2.php index d221f328364..ce34fa5aae2 100644 --- a/auth/oauth2/lang/en/auth_oauth2.php +++ b/auth/oauth2/lang/en/auth_oauth2.php @@ -24,6 +24,6 @@ $string['auth_oauth2description'] = 'OAuth 2 standards based authentication'; $string['auth_oauth2settings'] = 'OAuth 2 authentication settings.'; -$string['pluginname'] = 'OAuth 2'; -$string['plugindescription'] = 'This authentication plugin displays a list of the configured identity providers on the moodle login page. Selecting an identity provider allows users to login with their credentials from an OAuth 2 provider.'; $string['notloggedin'] = 'The login attempt failed.'; +$string['plugindescription'] = 'This authentication plugin displays a list of the configured identity providers on the moodle login page. Selecting an identity provider allows users to login with their credentials from an OAuth 2 provider.'; +$string['pluginname'] = 'OAuth 2'; diff --git a/auth/oauth2/login.php b/auth/oauth2/login.php index 835a39b9ba7..658c5ad134f 100644 --- a/auth/oauth2/login.php +++ b/auth/oauth2/login.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Open ID authentication. + * Open ID authentication. This file is a simple login entry point for OAuth identity providers. * * @package auth_oauth2 * @copyright 2017 Damyon Wiese diff --git a/lib/classes/oauth2/api.php b/lib/classes/oauth2/api.php index 0302d8bded0..3b4915e1de6 100644 --- a/lib/classes/oauth2/api.php +++ b/lib/classes/oauth2/api.php @@ -41,6 +41,10 @@ defined('MOODLE_INTERNAL') || die(); */ class api { + /** + * Create a google ready OAuth 2 service. + * @return core\oauth2\issuer + */ private static function create_google() { $record = (object) [ 'name' => 'Google', @@ -63,6 +67,10 @@ class api { return $issuer; } + /** + * Create a facebook ready OAuth 2 service. + * @return core\oauth2\issuer + */ private static function create_facebook() { // Facebook is a custom setup. $record = (object) [ @@ -115,6 +123,10 @@ class api { return $issuer; } + /** + * Create a microsoft ready OAuth 2 service. + * @return core\oauth2\issuer + */ private static function create_microsoft() { // Microsoft is a custom setup. $record = (object) [ @@ -182,26 +194,62 @@ class api { } } + /** + * List all the issuers, ordered by the sortorder field + * @return core\oauth2\issuer[] + */ public static function get_all_issuers() { return issuer::get_records([], 'sortorder'); } + /** + * Get a single issuer by id. + * + * @param int $id + * @return core\oauth2\issuer + */ public static function get_issuer($id) { return new issuer($id); } + /** + * Get a single endpoint by id. + * + * @param int $id + * @return core\oauth2\endpoint + */ public static function get_endpoint($id) { return new endpoint($id); } + /** + * Get a single user field mapping by id. + * + * @param int $id + * @return core\oauth2\user_field_mapping + */ public static function get_user_field_mapping($id) { return new user_field_mapping($id); } + /** + * Get the system account for an installed OAuth service. + * Never ever ever expose this to a webservice because it contains the refresh token which grants API access. + * + * @param int $id + * @return core\oauth2\user_field_mapping + */ public static function get_system_account(issuer $issuer) { return system_account::get_record(['issuerid' => $issuer->get('id')]); } + /** + * Get the full list of system scopes required by an oauth issuer. + * This includes the list required for login as well as any scopes injected by the oauth2_system_scopes callback in plugins. + * + * @param core\oauth2\issuer $issuer + * @return string + */ public static function get_system_scopes_for_issuer($issuer) { $scopes = $issuer->get('loginscopesoffline'); @@ -230,6 +278,13 @@ class api { return $scopes; } + /** + * Get an authenticated oauth2 client using the system account. + * This call uses the refresh token to get an access token. + * + * @param core\oauth2\issuer $issuer + * @return core\oauth2\client + */ public static function get_system_oauth_client(issuer $issuer) { $systemaccount = self::get_system_account($issuer); if (empty($systemaccount)) { @@ -248,6 +303,15 @@ class api { return $client; } + /** + * Get an authenticated oauth2 client using the current user account. + * This call does the redirect dance back to the current page after authentication. + * + * @param core\oauth2\issuer $issuer The desired OAuth issuer + * @param moodle_url $url The url to the current page. + * @param string $additionalscopes The additional scopes required for authorization. + * @return core\oauth2\client + */ public static function get_user_oauth_client(issuer $issuer, moodle_url $currenturl, $additionalscopes = '') { $client = new \core\oauth2\client($issuer, $currenturl, $additionalscopes); @@ -257,14 +321,31 @@ class api { return $client; } + /** + * Get the list of defined endpoints for this OAuth issuer + * + * @param core\oauth2\issuer $issuer The desired OAuth issuer + * @return core\oauth2\endpoint[] + */ public static function get_endpoints(issuer $issuer) { return endpoint::get_records(['issuerid' => $issuer->get('id')]); } + /** + * Get the list of defined mapping from OAuth user fields to moodle user fields. + * + * @param core\oauth2\issuer $issuer The desired OAuth issuer + * @return core\oauth2\user_field_mapping[] + */ public static function get_user_field_mappings(issuer $issuer) { return user_field_mapping::get_records(['issuerid' => $issuer->get('id')]); } + /** + * Guess an image from the discovery URL. + * + * @param core\oauth2\issuer $issuer The desired OAuth issuer + */ protected static function guess_image($issuer) { if (empty($issuer->get('image'))) { $baseurl = parse_url($issuer->get('discoveryurl')); @@ -362,6 +443,12 @@ class api { return endpoint::count_records(['issuerid' => $issuer->get('id')]); } + /** + * Take the data from the mform and update the issuer. + * + * @param stdClass $data + * @return core\oauth2\issuer + */ public static function update_issuer($data) { require_capability('moodle/site:config', context_system::instance()); $issuer = new issuer(0, $data); @@ -375,6 +462,12 @@ class api { return $issuer; } + /** + * Take the data from the mform and create the issuer. + * + * @param stdClass $data + * @return core\oauth2\issuer + */ public static function create_issuer($data) { require_capability('moodle/site:config', context_system::instance()); $issuer = new issuer(0, $data); @@ -388,6 +481,12 @@ class api { return $issuer; } + /** + * Take the data from the mform and update the endpoint. + * + * @param stdClass $data + * @return core\oauth2\endpoint + */ public static function update_endpoint($data) { require_capability('moodle/site:config', context_system::instance()); $endpoint = new endpoint(0, $data); @@ -398,6 +497,12 @@ class api { return $endpoint; } + /** + * Take the data from the mform and create the endpoint. + * + * @param stdClass $data + * @return core\oauth2\endpoint + */ public static function create_endpoint($data) { require_capability('moodle/site:config', context_system::instance()); $endpoint = new endpoint(0, $data); @@ -407,6 +512,12 @@ class api { return $endpoint; } + /** + * Take the data from the mform and update the user field mapping. + * + * @param stdClass $data + * @return core\oauth2\user_field_mapping + */ public static function update_user_field_mapping($data) { require_capability('moodle/site:config', context_system::instance()); $userfieldmapping = new user_field_mapping(0, $data); @@ -417,6 +528,12 @@ class api { return $userfieldmapping; } + /** + * Take the data from the mform and create the user field mapping. + * + * @param stdClass $data + * @return core\oauth2\user_field_mapping + */ public static function create_user_field_mapping($data) { require_capability('moodle/site:config', context_system::instance()); $userfieldmapping = new user_field_mapping(0, $data); @@ -459,6 +576,14 @@ class api { return $result; } + /** + * Reorder this identity issuer. + * + * Requires moodle/site:config capability at the system context. + * + * @param int $id The id of the identity issuer to move. + * @return boolean + */ public static function move_down_issuer($id) { require_capability('moodle/site:config', context_system::instance()); $current = new issuer($id); @@ -488,6 +613,14 @@ class api { return $result; } + /** + * Delete an identity issuer. + * + * Requires moodle/site:config capability at the system context. + * + * @param int $id The id of the identity issuer to delete. + * @return boolean + */ public static function delete_issuer($id) { require_capability('moodle/site:config', context_system::instance()); $issuer = new issuer($id); @@ -507,6 +640,14 @@ class api { return $issuer->delete(); } + /** + * Delete an endpoint. + * + * Requires moodle/site:config capability at the system context. + * + * @param int $id The id of the endpoint to delete. + * @return boolean + */ public static function delete_endpoint($id) { require_capability('moodle/site:config', context_system::instance()); $endpoint = new endpoint($id); @@ -515,6 +656,14 @@ class api { return $endpoint->delete(); } + /** + * Delete a user_field_mapping. + * + * Requires moodle/site:config capability at the system context. + * + * @param int $id The id of the user_field_mapping to delete. + * @return boolean + */ public static function delete_user_field_mapping($id) { require_capability('moodle/site:config', context_system::instance()); $userfieldmapping = new user_field_mapping($id); @@ -523,6 +672,15 @@ class api { return $userfieldmapping->delete(); } + /** + * Perform the OAuth dance and get a refresh token. + * + * Requires moodle/site:config capability at the system context. + * + * @param core\oauth2\issuer $issuer + * @param moodle_url $returnurl The url to the current page (we will be redirected back here after authentication). + * @return boolean + */ public static function connect_system_account($issuer, $returnurl) { require_capability('moodle/site:config', context_system::instance()); diff --git a/lib/classes/oauth2/client.php b/lib/classes/oauth2/client.php index 3947b0c3dac..4595205c1bd 100644 --- a/lib/classes/oauth2/client.php +++ b/lib/classes/oauth2/client.php @@ -146,6 +146,11 @@ class client extends \oauth2_client { return $name; } + /** + * Get a list of the mapping user fields in an associative array. + * + * @return array + */ protected function get_userinfo_mapping() { $fields = user_field_mapping::get_records(['issuerid' => $this->issuer->get('id')]); @@ -215,6 +220,12 @@ class client extends \oauth2_client { return true; } + /** + * Fetch the user info from the user info endpoint and map all + * the fields back into moodle fields. + * + * @return array (Moodle user fields for the logged in user). + */ public function get_userinfo() { $url = $this->get_issuer()->get_endpoint_url('userinfo'); $response = $this->get($url); diff --git a/lib/classes/oauth2/endpoint.php b/lib/classes/oauth2/endpoint.php index 5916455a172..3a62e13e6d5 100644 --- a/lib/classes/oauth2/endpoint.php +++ b/lib/classes/oauth2/endpoint.php @@ -57,6 +57,13 @@ class endpoint extends persistent { ); } + /** + * Custom validator for end point URLs. + * Because we send Bearer tokens we must ensure SSL. + * + * @param $value The value to check. + * @return boolean + */ protected function validate_url($value) { if (strpos($value, 'https://') !== 0) { return new lang_string('sslonlyaccess', 'error'); diff --git a/lib/classes/oauth2/issuer.php b/lib/classes/oauth2/issuer.php index 040d3e66ae8..5755c5062cd 100644 --- a/lib/classes/oauth2/issuer.php +++ b/lib/classes/oauth2/issuer.php @@ -96,6 +96,11 @@ class issuer extends persistent { ); } + /** + * Helper the get a named service endpoint. + * @param string $type + * @return string|false + */ public function get_endpoint_url($type) { $endpoint = endpoint::get_record([ 'issuerid' => $this->get('id'), @@ -108,14 +113,26 @@ class issuer extends persistent { return false; } + /** + * Does this OAuth service support user authentication? + * @return boolean + */ public function is_authentication_supported() { return (!empty($this->get_endpoint_url('userinfo'))); } + /** + * Does this OAuth service support system authentication? + * @return boolean + */ public function is_system_account_setup_supported() { return true; } + /** + * Do we have a refresh token for a system account? + * @return boolean + */ public function is_system_account_connected() { $sys = system_account::get_record(['issuerid' => $this->get('id')]); if (!empty($sys) and !empty($sys->get('refreshtoken'))) { diff --git a/lib/classes/oauth2/user_field_mapping.php b/lib/classes/oauth2/user_field_mapping.php index 1ccf26b0e9e..0cf0178a5e2 100644 --- a/lib/classes/oauth2/user_field_mapping.php +++ b/lib/classes/oauth2/user_field_mapping.php @@ -72,6 +72,11 @@ class user_field_mapping extends persistent { ); } + /** + * Return the list of internal fields + * in a format they can be used for choices in a select menu + * @return array + */ public function get_internalfield_list() { return array_combine(self::$userfields, self::$userfields); } diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php index 5f54b5621ce..88193ff83b4 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -1701,7 +1701,7 @@ class core_plugin_manager { 'auth' => array( 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'lti', 'manual', 'mnet', - 'nntp', 'nologin', 'none', 'pam', 'pop3', 'shibboleth', 'webservice' + 'nntp', 'nologin', 'none', 'oauth2', 'pam', 'pop3', 'shibboleth', 'webservice' ), 'availability' => array( @@ -1902,7 +1902,7 @@ class core_plugin_manager { 'assignmentupgrade', 'availabilityconditions', 'behat', 'capability', 'cohortroles', 'customlang', 'dbtransfer', 'filetypes', 'generator', 'health', 'innodb', 'installaddon', 'langimport', 'log', 'lp', 'lpimportcsv', 'lpmigrate', 'messageinbound', 'mobile', 'multilangupgrade', 'monitor', - 'phpunit', 'profiling', 'recyclebin', 'replace', 'spamcleaner', 'task', 'templatelibrary', + 'oauth2', 'phpunit', 'profiling', 'recyclebin', 'replace', 'spamcleaner', 'task', 'templatelibrary', 'unittest', 'uploadcourse', 'uploaduser', 'unsuproles', 'usertours', 'xmldb' ),