91334a46b5
Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
6.3 KiB
PHP
176 lines
6.3 KiB
PHP
<?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/>.
|
|
|
|
/**
|
|
* Adobe Connect SSO helpers for the current Moodle user.
|
|
*
|
|
* @package mod_meetletacbfree
|
|
* @copyright 2026 MeetLet <info@meetlet.net>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
namespace mod_meetletacbfree\local;
|
|
|
|
use mod_meetletacbfree\api\client;
|
|
use mod_meetletacbfree\api\service;
|
|
use moodle_exception;
|
|
use stdClass;
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
/**
|
|
* Build Connect logins and BREEZESESSION for Moodle users.
|
|
*/
|
|
class sso {
|
|
/**
|
|
* Ordered Connect login candidates for a Moodle user.
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public static function login_candidates(stdClass $user, ?stdClass $adobeuser = null): array {
|
|
$reserved = [
|
|
'administrators', 'authors', 'everyone', 'live-admins', 'admins',
|
|
'course-admins', 'event-administrators', 'event-managers', 'learners',
|
|
'seminar-administrators',
|
|
// Generic Moodle accounts must never be sent as Connect SSO identities.
|
|
'admin', 'guest', 'root', 'administrator', 'moodle',
|
|
];
|
|
$candidates = [];
|
|
// Prefer ext-login (refresh_principal_login) then stable mlu{id}, then legacy logins.
|
|
$seed = [];
|
|
if (!empty($adobeuser->login)) {
|
|
$seed[] = (string)$adobeuser->login;
|
|
}
|
|
if (!empty($adobeuser->internallogin)) {
|
|
$seed[] = (string)$adobeuser->internallogin;
|
|
}
|
|
$seed[] = config::connect_login($user);
|
|
foreach (array_merge($seed, config::legacy_connect_logins($user)) as $candidate) {
|
|
$candidate = trim((string)$candidate);
|
|
if ($candidate === '' || in_array(strtolower($candidate), $reserved, true)) {
|
|
continue;
|
|
}
|
|
if (!in_array($candidate, $candidates, true)) {
|
|
$candidates[] = $candidate;
|
|
}
|
|
}
|
|
return $candidates;
|
|
}
|
|
|
|
/**
|
|
* Obtain a BREEZESESSION cookie via HTTP header SSO for the given logins.
|
|
*
|
|
* @param string[] $logincandidates
|
|
*/
|
|
public static function breeze_session(array $logincandidates): string {
|
|
\mod_meetletacbfree\registration\manager::require_registered();
|
|
|
|
$cookie = '';
|
|
$lasterror = null;
|
|
$primary = $logincandidates[0] ?? '';
|
|
|
|
foreach ($logincandidates as $logintry) {
|
|
try {
|
|
$sso = new client();
|
|
$cookie = $sso->login_external($logintry);
|
|
if ($cookie !== '') {
|
|
return $cookie;
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$lasterror = $e;
|
|
}
|
|
}
|
|
$parsed = $lasterror ? usererror::from_throwable($lasterror) : ['helpid' => 'sso_01', 'facts' => [], 'xml' => ''];
|
|
$facts = $parsed['facts'];
|
|
if ($primary !== '' && empty($facts['login'])) {
|
|
$facts['login'] = $primary;
|
|
}
|
|
$ip = self::guess_egress_ip();
|
|
if ($ip !== '') {
|
|
$facts['egress_ip'] = $ip;
|
|
}
|
|
$helpid = $parsed['helpid'];
|
|
if ($lasterror instanceof moodle_exception && $lasterror->errorcode === 'agentrequestfailed') {
|
|
$helpid = 'sso_02';
|
|
$facts['agent'] = is_scalar($lasterror->a) ? (string)$lasterror->a : $lasterror->getMessage();
|
|
}
|
|
if ($lasterror instanceof usererror && $lasterror->helpid === 'sso_01') {
|
|
$helpid = 'sso_01';
|
|
}
|
|
throw new usererror($helpid, 'errorhttpauth', $facts, $parsed['xml']);
|
|
}
|
|
|
|
/**
|
|
* Best-effort public IP Adobe will see for Moodle API calls.
|
|
*/
|
|
public static function guess_egress_ip(): string {
|
|
try {
|
|
$curl = new \curl(['ignoresecurity' => true]);
|
|
$curl->setopt(['CURLOPT_TIMEOUT' => 5]);
|
|
$raw = $curl->get('https://api.ipify.org');
|
|
$ip = trim((string)$raw);
|
|
if (preg_match('/^\d{1,3}(?:\.\d{1,3}){3}$/', $ip)) {
|
|
return $ip;
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Ignore.
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Ensure Connect user exists, grant permission on a SCO (and parent meeting for
|
|
* recordings), then SSO login.
|
|
*
|
|
* @return array{cookie:string,adobeuser:stdClass}
|
|
*/
|
|
public static function provision_and_login(stdClass $user, int $scoid, string $permission): array {
|
|
\mod_meetletacbfree\registration\manager::require_registered();
|
|
|
|
$service = new service();
|
|
$service->login_admin();
|
|
$adobeuser = $service->ensure_user($user);
|
|
$adobeuser = $service->refresh_principal_login($adobeuser);
|
|
if (trim((string)($adobeuser->login ?? '')) === '') {
|
|
throw new usererror('usr_01', 'errorcreateuser', ['login' => config::connect_login($user)]);
|
|
}
|
|
|
|
$targets = [$scoid];
|
|
try {
|
|
$info = $service->get_client()->request([
|
|
'action' => 'sco-info',
|
|
'sco-id' => $scoid,
|
|
]);
|
|
$folder = (int)($info->sco['folder-id'] ?? 0);
|
|
// Parent meeting folder ACL is required for recordings and uploaded content.
|
|
if ($folder > 0) {
|
|
$targets[] = $folder;
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Best-effort enrichment only.
|
|
}
|
|
foreach (array_unique($targets) as $aclid) {
|
|
if ($aclid > 0) {
|
|
$service->assign_permission((int)$adobeuser->principalid, (int)$aclid, $permission);
|
|
}
|
|
}
|
|
$service->logout();
|
|
|
|
$cookie = self::breeze_session(self::login_candidates($user, $adobeuser));
|
|
return ['cookie' => $cookie, 'adobeuser' => $adobeuser];
|
|
}
|
|
}
|