Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion components/ILIAS/Init/classes/class.ilInitialisation.php
Original file line number Diff line number Diff line change
Expand Up @@ -1452,7 +1452,6 @@ protected static function initHTML(): void
$_GET['offset'] = (int) $_GET['offset']; // old code
}

$GLOBALS["DIC"]["lti"]->init();
self::initKioskMode($GLOBALS["DIC"]);
}
}
Expand Down
52 changes: 0 additions & 52 deletions components/ILIAS/Init/classes/class.ilStartUpGUI.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ public function getSafePostCommands(): array
{
return [
'doStandardAuthentication',
'doLTIAuthentication'
];
}

Expand Down Expand Up @@ -602,57 +601,6 @@ static function (ILIAS\Data\Password $value): string {
->withAdditionalTransformation($this->saniziteArrayElementsTrafo());
}

private function doLTIAuthentication(): void
{
$this->getLogger()->debug('Trying lti authentication');

$credentials = new ilAuthFrontendCredentialsLTI();
$credentials->initFromRequest();

$provider_factory = new ilAuthProviderFactory();
$provider = $provider_factory->getProviderByAuthMode($credentials, ilAuthUtils::AUTH_PROVIDER_LTI);

$status = ilAuthStatus::getInstance();

$frontend_factory = new ilAuthFrontendFactory();
$frontend_factory->setContext(ilAuthFrontendFactory::CONTEXT_STANDARD_FORM);
$frontend = $frontend_factory->getFrontend(
$this->authSession,
$status,
$credentials,
[$provider]
);
$frontend->authenticate();

$lti_target = $this->dic['lti']->getLaunchTarget();
if ($lti_target !== '') {
ilSession::set('orig_request_target', $lti_target);
}

switch ($status->getStatus()) {
case ilAuthStatus::STATUS_AUTHENTICATED:
ilLTISessionCookie::allowCrossSite();
// the user of a launch goes to the object the platform launched
if ($lti_target !== '') {
$this->ctrl->redirectToURL($lti_target);
}
ilLoggerFactory::getLogger('auth')->debug('Authentication successful; Redirecting to starting page.');
ilInitialisation::redirectToStartingPage();

// no break
case ilAuthStatus::STATUS_ACCOUNT_MIGRATION_REQUIRED:
$this->ctrl->redirect($this, 'showAccountMigration');

// no break
case ilAuthStatus::STATUS_AUTHENTICATION_FAILED:
$this->mainTemplate->setOnScreenMessage('failure', $status->getTranslatedReason(), true);
$this->ctrl->redirect($this, 'showLoginPage');
}

$this->mainTemplate->setOnScreenMessage('failure', $this->lng->txt('err_wrong_login'));
$this->showLoginPage();
}

private function doApacheAuthentication(): void
{
$this->getLogger()->debug('Trying apache authentication');
Expand Down
99 changes: 40 additions & 59 deletions components/ILIAS/LTI/LTI.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,6 @@

namespace ILIAS;

use ArrayAccess;
use Closure;
use ILIAS\DI\Container;
use ILIAS\DI\UIServices;
use ILIAS\GlobalScreen\Services as GlobalScreen;
use ILIAS\HTTP\GlobalHttpState;
use ILIAS\LTI\View as LTIView;
use ILIAS\Refinery\Factory as Refinery;
use ILIAS\Setup\Agent;
use ILIAS\UI\Factory as UIFactory;
use ILIAS\UI\Renderer as UIRenderer;
use ilAuthSession;
use ilCtrl;
use ilLanguage;
use ilLTISetupAgent;
use ilLTIViewGUI;
use ilObjUser;
use ReflectionClass;

/**
* LTI component: LTI 1.1 (classes/LTI1p1) and LTI Advantage (classes/LTIAdvantage) for ILIAS as consumer and provider.
* See README.md for the structure.
Expand All @@ -48,31 +29,22 @@
class LTI implements Component\Component
{
public function init(
array | ArrayAccess &$define,
array | ArrayAccess &$implement,
array | ArrayAccess &$use,
array | ArrayAccess &$contribute,
array | ArrayAccess &$seek,
array | ArrayAccess &$provide,
array | ArrayAccess &$pull,
array | ArrayAccess &$internal,
array | \ArrayAccess &$define,
array | \ArrayAccess &$implement,
array | \ArrayAccess &$use,
array | \ArrayAccess &$contribute,
array | \ArrayAccess &$seek,
array | \ArrayAccess &$provide,
array | \ArrayAccess &$pull,
array | \ArrayAccess &$internal,
): void {
$define[] = LTIView::class;

$implement[LTIView::class] = static fn() => new ilLTIViewGUI(
$use[GlobalHttpState::class],
$pull[Refinery::class],
$use[UIFactory::class],
$use[UIRenderer::class],
self::legacy(UIServices::class, static fn(Container $dic): UIServices => $dic->ui()),
self::legacy(ilCtrl::class, static fn(Container $dic): ilCtrl => $dic['ilCtrl']),
self::legacy(ilObjUser::class, static fn(Container $dic): ilObjUser => $dic['ilUser']),
self::legacy(ilLanguage::class, static fn(Container $dic): ilLanguage => $dic['lng']),
self::legacy(GlobalScreen::class, static fn(Container $dic): GlobalScreen => $dic['global_screen']),
self::legacy(ilAuthSession::class, static fn(Container $dic): ilAuthSession => $dic['ilAuthSession']),
);

$contribute[Agent::class] = static fn() => new ilLTISetupAgent($pull[Refinery::class]);
$define[] = \ILIAS\LTI\View::class;

$implement[\ILIAS\LTI\View::class] = static fn(): \ILIAS\LTI\View =>
$internal[\ilLTIViewGUI::class];

$contribute[\ILIAS\Setup\Agent::class] = static fn(): \ILIAS\Setup\Agent =>
new \ilLTISetupAgent($pull[\ILIAS\Refinery\Factory::class]);

$contribute[Component\Resource\PublicAsset::class] = fn() =>
new Component\Resource\Endpoint($this, "ltiresult.php");
Expand Down Expand Up @@ -106,22 +78,31 @@ public function init(

$contribute[Component\Resource\PublicAsset::class] = fn() =>
new Component\Resource\ComponentCSS($this, "lti_view.css");
}

/**
* A service the legacy initialisation still sets up, which the LTI view only gets once it uses it.
*
* @template T of object
* @param class-string<T> $class
* @param Closure(Container): T $get
* @return T
*/
private static function legacy(string $class, Closure $get): object
{
return new ReflectionClass($class)->newLazyProxy(static function () use ($get): object {
global $DIC;

return $get($DIC);
});
$internal[\ilLTIViewGUI::class] = static fn(): \ilLTIViewGUI =>
new \ilLTIViewGUI(
$use[\ILIAS\HTTP\GlobalHttpState::class],
$pull[\ILIAS\Refinery\Factory::class],
$use[\ILIAS\UI\Factory::class],
$use[\ILIAS\UI\Renderer::class],
$use[\ILIAS\UICore\GlobalTemplate::class],
$use[\ILIAS\Language\Language::class],
$internal[\ILIAS\LTI\View\CurrentUser::class],
$internal[\ILIAS\LTI\View\Navigation::class],
$internal[\ILIAS\LTI\View\ScreenContext::class],
$internal[\ILIAS\LTI\View\AuthSession::class],
);

$internal[\ILIAS\LTI\View\CurrentUser::class] = static fn(): \ILIAS\LTI\View\CurrentUser =>
new \ILIAS\LTI\View\CurrentUserLegacyProxy();

$internal[\ILIAS\LTI\View\Navigation::class] = static fn(): \ILIAS\LTI\View\Navigation =>
new \ILIAS\LTI\View\NavigationLegacyProxy();

$internal[\ILIAS\LTI\View\ScreenContext::class] = static fn(): \ILIAS\LTI\View\ScreenContext =>
new \ILIAS\LTI\View\ScreenContextLegacyProxy();

$internal[\ILIAS\LTI\View\AuthSession::class] = static fn(): \ILIAS\LTI\View\AuthSession =>
new \ILIAS\LTI\View\AuthSessionLegacyProxy();
}
}
9 changes: 5 additions & 4 deletions components/ILIAS/LTI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ LTI/
│ └── Setup/ Setup agent and database update steps
├── resources/ Endpoints
├── src/ What the component provides to the others through the bootstrap (ILIAS\LTI\View)
│ └── View/ What the LTI view needs of services not in the bootstrap yet, with their legacy proxies
└── templates/default/ Templates of LTI 1.1 are prefixed with tpl.lti1p1_
```

Expand All @@ -59,9 +60,9 @@ LTI/
* **LTIAdvantage/Common**: only what the Advantage platform and tool roles both use.
* **Administration**: the administration node (`ilObjLTIAdministration*`). Its screens serve LTI 1.1 and LTI Advantage.
* **Tool**: ILIAS as the tool a platform launches. celtic/lti checks a launch of either version by its
data, so the launch (`ilLTILaunchReceiver`, `ilLTIDataConnector`), the login (`ilAuthProviderLTI`), the
LTI view (`ilLTIViewGUI`, which `LTI.php` provides as `ILIAS\LTI\View` and Init passes to the legacy
environment as `$DIC['lti']`), the releases of objects (`ilLTIProviderObjectSettingGUI`, `ilLTIRelease`) and
data, so the launch (`ilLTILaunchReceiver`, `ilLTIDataConnector`), the login (`ilLTILoginGUI`,
`ilAuthProviderLTI`), the LTI view (`ilLTIViewGUI`, which `LTI.php` provides as `ILIAS\LTI\View` and Init
passes to the legacy environment as `$DIC['lti']`; `ilLTIViewLayoutProvider` shapes its pages), the releases of objects (`ilLTIProviderObjectSettingGUI`, `ilLTIRelease`) and
the learning progress sent back (`ilLTIAppEventListener`, `ilLTICronOutcomeService`) are shared. Only
what a version adds on top goes to LTI1p1 or LTIAdvantage. Several of these names are fixed by the core:
`ilAuthProviderLTI`, `ilAuthFrontendCredentialsLTI`, `ilLTIProviderObjectSettingGUI`,
Expand Down Expand Up @@ -104,7 +105,7 @@ the endpoints:
default tool of the library, with its PEM key or key set URL. A launch starts the OpenID Connect login at
the tool, which gets the id_token from `ltiauth.php`; the login waits in the session and is used once.
Tools often ask `ltiauth.php` with a POST from their own site, which does not carry a SameSite=Lax cookie,
so the page that starts the launch sends the session cookie again as SameSite=None, as `ilStartUpGUI`
so the page that starts the launch sends the session cookie again as SameSite=None, as `ilLTILoginGUI`
does for LTI sessions. An embedded launch also offers the platform storage of LTI (`Platform::getStorageJS()`
of celtic/lti in the page around the iframe), for tools that cannot keep a cookie inside an iframe.
`ltitoken.php` checks the client assertion of a tool and issues access tokens for the Assignment and
Expand Down
119 changes: 119 additions & 0 deletions components/ILIAS/LTI/classes/Tool/class.ilLTILoginGUI.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

declare(strict_types=1);

/**
* Logs the user of a launch in, see lti.php. ILIAS goes on to the object the platform launched, or back to
* the login page of ilStartUpGUI when the login was not accepted.
*
* @author Fabian Schmid <fabian@sr.solutions>
*/
class ilLTILoginGUI implements ilCtrlBaseClassInterface, ilCtrlSecurityInterface
{
public const string CMD_AUTHENTICATE = 'doLTIAuthentication';

private readonly ilCtrlInterface $ctrl;
private readonly ilGlobalTemplateInterface $main_tpl;
private readonly ilAuthSession $auth_session;
private readonly ilLanguage $lng;
private readonly ilLogger $logger;

public function __construct()
{
global $DIC;

$this->ctrl = $DIC->ctrl();
$this->main_tpl = $DIC->ui()->mainTemplate();
$this->auth_session = $DIC['ilAuthSession'];
$this->lng = $DIC->language();
$this->logger = $DIC->logger()->forComponent('lti');
}

public function getUnsafeGetCommands(): array
{
return [];
}

/**
* A platform cannot send the token of ilCtrl.
*/
public function getSafePostCommands(): array
{
return [self::CMD_AUTHENTICATE];
}

public function executeCommand(): void
{
if ($this->ctrl->getCmd() === self::CMD_AUTHENTICATE) {
$this->authenticate();
}

$this->ctrl->redirectByClass(ilStartUpGUI::class, 'showLoginPage');
}

private function authenticate(): void
{
$this->logger->debug('Trying lti authentication');

$credentials = new ilAuthFrontendCredentialsLTI();
$credentials->initFromRequest();

$provider_factory = new ilAuthProviderFactory();
$provider = $provider_factory->getProviderByAuthMode($credentials, ilAuthUtils::AUTH_PROVIDER_LTI);

$status = ilAuthStatus::getInstance();

$frontend_factory = new ilAuthFrontendFactory();
$frontend_factory->setContext(ilAuthFrontendFactory::CONTEXT_STANDARD_FORM);
$frontend = $frontend_factory->getFrontend(
$this->auth_session,
$status,
$credentials,
[$provider]
);
$frontend->authenticate();

$lti_target = ilLTIViewGUI::getLaunchTarget();
if ($lti_target !== '') {
ilSession::set('orig_request_target', $lti_target);
}

switch ($status->getStatus()) {
case ilAuthStatus::STATUS_AUTHENTICATED:
ilLTISessionCookie::allowCrossSite();
// the user of a launch goes to the object the platform launched
if ($lti_target !== '') {
$this->ctrl->redirectToURL($lti_target);
}
$this->logger->debug('Authentication successful; Redirecting to starting page.');
ilInitialisation::redirectToStartingPage();

// no break
case ilAuthStatus::STATUS_ACCOUNT_MIGRATION_REQUIRED:
$this->ctrl->redirectByClass(ilStartUpGUI::class, 'showAccountMigration');

// no break
case ilAuthStatus::STATUS_AUTHENTICATION_FAILED:
$this->main_tpl->setOnScreenMessage('failure', $status->getTranslatedReason(), true);
$this->ctrl->redirectByClass(ilStartUpGUI::class, 'showLoginPage');
}

$this->main_tpl->setOnScreenMessage('failure', $this->lng->txt('err_wrong_login'), true);
}
}
Loading
Loading