MDL-65637 core_oauth2: Introduce a new custom linkedin oauth2 client
This commit is contained in:
@@ -222,6 +222,12 @@ if ($mform && $mform->is_cancelled()) {
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
|
||||
echo $renderer->single_button($addurl, get_string('createnewnextcloudissuer', 'tool_oauth2'));
|
||||
|
||||
// Linkedin template.
|
||||
$docs = 'admin/tool/oauth2/issuers/linkedin';
|
||||
$params = ['action' => 'edittemplate', 'type' => 'linkedin', 'sesskey' => sesskey(), 'docslink' => $docs];
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
|
||||
echo $renderer->single_button($addurl, get_string('linkedin_service', 'tool_oauth2'));
|
||||
|
||||
// Generic issuer.
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', ['action' => 'edit']);
|
||||
echo $renderer->single_button($addurl, get_string('createnewissuer', 'tool_oauth2'));
|
||||
|
||||
@@ -85,6 +85,7 @@ $string['issuershowonloginpage'] = 'Show on login page';
|
||||
$string['issuerrequireconfirmation_help'] = 'Require that all users verify their email address before they can log in with OAuth. This applies to newly created accounts as part of the login process, or when an existing Moodle account is connected to an OAuth login via matching email addresses.';
|
||||
$string['issuerrequireconfirmation'] = 'Require email verification';
|
||||
$string['issuers'] = 'Issuers';
|
||||
$string['linkedin_service'] = 'Create new LinkedIn service';
|
||||
$string['loginissuer'] = 'Allow login';
|
||||
$string['notconfigured'] = 'Not configured';
|
||||
$string['notdiscovered'] = 'Service discovery not successful';
|
||||
|
||||
@@ -289,6 +289,68 @@ class api {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a linkedin OAuth2 issuer
|
||||
*
|
||||
* @return issuer
|
||||
*/
|
||||
private static function init_linkedin(): issuer {
|
||||
$record = (object) [
|
||||
'name' => 'LinkedIn',
|
||||
'image' => 'https://static.licdn.com/scds/common/u/images/logos/favicons/v1/favicon.ico',
|
||||
'baseurl' => 'https://api.linkedin.com/v2',
|
||||
'loginscopes' => 'r_liteprofile r_emailaddress',
|
||||
'loginscopesoffline' => 'r_liteprofile r_emailaddress',
|
||||
'showonloginpage' => true
|
||||
];
|
||||
|
||||
$issuer = new issuer(0, $record);
|
||||
return $issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create endpoints for linkedin issuers.
|
||||
*
|
||||
* @param issuer $issuer
|
||||
* @throws \coding_exception
|
||||
* @throws \core\invalid_persistent_exception
|
||||
*/
|
||||
private static function create_endpoints_for_linkedin(issuer $issuer) {
|
||||
$endpoints = [
|
||||
'authorization_endpoint' => 'https://www.linkedin.com/oauth/v2/authorization',
|
||||
'token_endpoint' => 'https://www.linkedin.com/oauth/v2/accessToken',
|
||||
'email_endpoint' => 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))',
|
||||
'userinfo_endpoint' => "https://api.linkedin.com/v2/me?projection=(localizedFirstName,localizedLastName,"
|
||||
. "profilePicture(displayImage~digitalmediaAsset:playableStreams))",
|
||||
];
|
||||
foreach ($endpoints as $name => $url) {
|
||||
$record = (object) [
|
||||
'issuerid' => $issuer->get('id'),
|
||||
'name' => $name,
|
||||
'url' => $url
|
||||
];
|
||||
$endpoint = new endpoint(0, $record);
|
||||
$endpoint->create();
|
||||
}
|
||||
|
||||
// Create the field mappings.
|
||||
$mapping = [
|
||||
'localizedFirstName' => 'firstname',
|
||||
'localizedLastName' => 'lastname',
|
||||
'elements[0]-handle~-emailAddress' => 'email',
|
||||
'profilePicture-displayImage~-elements[0]-identifiers[0]-identifier' => 'picture'
|
||||
];
|
||||
foreach ($mapping as $external => $internal) {
|
||||
$record = (object) [
|
||||
'issuerid' => $issuer->get('id'),
|
||||
'externalfield' => $external,
|
||||
'internalfield' => $internal
|
||||
];
|
||||
$userfieldmapping = new user_field_mapping(0, $record);
|
||||
$userfieldmapping->create();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a record for one of the standard issuers to be displayed in the settings.
|
||||
* The issuer is not yet created in the database.
|
||||
@@ -305,6 +367,8 @@ class api {
|
||||
return self::init_facebook();
|
||||
} else if ($type == 'nextcloud') {
|
||||
return self::init_nextcloud();
|
||||
} else if ($type == 'linkedin') {
|
||||
return self::init_linkedin();
|
||||
} else {
|
||||
throw new moodle_exception('OAuth 2 service type not recognised: ' . $type);
|
||||
}
|
||||
@@ -328,6 +392,8 @@ class api {
|
||||
return self::create_endpoints_for_facebook($issuer);
|
||||
} else if ($type == 'nextcloud') {
|
||||
return self::create_endpoints_for_nextcloud($issuer);
|
||||
} else if ($type == 'linkedin') {
|
||||
return self::create_endpoints_for_linkedin($issuer);
|
||||
} else {
|
||||
throw new moodle_exception('OAuth 2 service type not recognised: ' . $type);
|
||||
}
|
||||
@@ -466,8 +532,8 @@ class api {
|
||||
}
|
||||
// Get all the scopes!
|
||||
$scopes = self::get_system_scopes_for_issuer($issuer);
|
||||
|
||||
$client = new \core\oauth2\client($issuer, null, $scopes, true);
|
||||
$class = self::get_client_classname($issuer->get('name'));
|
||||
$client = new $class($issuer, null, $scopes, true);
|
||||
|
||||
if (!$client->is_logged_in()) {
|
||||
if (!$client->upgrade_refresh_token($systemaccount)) {
|
||||
@@ -489,11 +555,30 @@ class api {
|
||||
*/
|
||||
public static function get_user_oauth_client(issuer $issuer, moodle_url $currenturl, $additionalscopes = '',
|
||||
$autorefresh = false) {
|
||||
$client = new \core\oauth2\client($issuer, $currenturl, $additionalscopes, false, $autorefresh);
|
||||
$class = self::get_client_classname($issuer->get('name'));
|
||||
$client = new $class($issuer, $currenturl, $additionalscopes, false, $autorefresh);
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client classname for an issuer.
|
||||
*
|
||||
* @param string $type The OAuth issuer name
|
||||
* @return string The classname for the custom client or core client class if the class for the defined type
|
||||
* doesn't exist or null type is defined.
|
||||
*/
|
||||
protected static function get_client_classname(?string $type): string {
|
||||
// Default core client class.
|
||||
$classname = 'core\\oauth2\\client';
|
||||
|
||||
if (strpos(strtolower($type), 'linkedin') !== false) {
|
||||
$classname = 'core\\oauth2\\client\\linkedin';
|
||||
}
|
||||
|
||||
return $classname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of defined endpoints for this OAuth issuer
|
||||
*
|
||||
@@ -893,8 +978,8 @@ class api {
|
||||
$scopes = self::get_system_scopes_for_issuer($issuer);
|
||||
|
||||
// Allow callbacks to inject non-standard scopes to the auth request.
|
||||
|
||||
$client = new client($issuer, $returnurl, $scopes, true);
|
||||
$class = self::get_client_classname($issuer->get('name'));
|
||||
$client = new $class($issuer, $returnurl, $scopes, true);
|
||||
|
||||
if (!optional_param('response', false, PARAM_BOOL)) {
|
||||
$client->log_out();
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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/>.
|
||||
|
||||
namespace core\oauth2\client;
|
||||
|
||||
use core\oauth2\client;
|
||||
|
||||
/**
|
||||
* Class linkedin - Custom client handler to fetch data from linkedin
|
||||
*
|
||||
* Custom oauth2 client for linkedin as it doesn't support OIDC and has a different way to get
|
||||
* key information for users - firstname, lastname, email.
|
||||
*
|
||||
* @copyright 2021 Peter Dias
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @package core
|
||||
*/
|
||||
class linkedin extends client {
|
||||
/**
|
||||
* Fetch the user info from the userinfo and email endpoint and map fields back
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function get_userinfo() {
|
||||
$user = array_merge(parent::get_userinfo(), $this->get_useremail());
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the email address of the user from the email endpoint
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
private function get_useremail() {
|
||||
$url = $this->get_issuer()->get_endpoint_url('email');
|
||||
|
||||
$response = $this->get($url);
|
||||
if (!$response) {
|
||||
return false;
|
||||
}
|
||||
$userinfo = new \stdClass();
|
||||
try {
|
||||
$userinfo = json_decode($response);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->map_userinfo_to_fields($userinfo);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3851,7 +3851,7 @@
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The time this record was created."/>
|
||||
<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="The oauth issuer."/>
|
||||
<FIELD NAME="externalfield" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false" COMMENT="The fieldname returned by the userinfo endpoint."/>
|
||||
<FIELD NAME="externalfield" TYPE="char" LENGTH="500" NOTNULL="true" SEQUENCE="false" COMMENT="The fieldname returned by the userinfo endpoint."/>
|
||||
<FIELD NAME="internalfield" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false" COMMENT="The name of the Moodle field this user field maps to."/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
|
||||
@@ -3059,5 +3059,15 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2020110903.05);
|
||||
}
|
||||
|
||||
if ($oldversion < 2020110904.05) {
|
||||
// Update the externalfield to be larger.
|
||||
$table = new xmldb_table('oauth2_user_field_mapping');
|
||||
$field = new xmldb_field('externalfield', XMLDB_TYPE_CHAR, '500', null, XMLDB_NOTNULL, false, null, 'issuerid');
|
||||
$dbman->change_field_type($table, $field);
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2020110904.05);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2020110904.04; // 20201109 = branching date YYYYMMDD - do not modify!
|
||||
$version = 2020110904.05; // 20201109 = branching date YYYYMMDD - do not modify!
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '3.10.4+ (Build: 20210527)';// Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user