Core Anpassungen
Zweck
Um das durch die oauth2
App implementierte OAuth 2.0 Protokoll für den Zugriff auf die WebDAV Schnittstelle und die OCS Share API nutzen zu können, mussten Anpassungen am ownCloud Core durchgeführt werden. Diese Änderungen liegen im Pull Request owncloud/core#26742 vor. Ein Backport für ownCloud 9.1 ist im Pull Request owncloud/core#27370 zu finden.
Vorgegebene Schnittstelle
Bevor wir ownCloud um OAuth 2.0 erweitern konnten, mussten wir die vorgegebene Schnittstelle untersuchen. Dazu schauten wir uns die Implementierungen von WebDAV und der OCS Share API an.
WebDAV
Bei der Untersuchung der WebDAV Schnittstelle stellte sich heraus, dass hierfür eine ownCloud App namens dav
verwendet wurde, die die sabre/dav Bibliothek zur Implementierung des WebDAV-Serves nutzt. Da die Bibliothek den Austausch der Authentifizierung über eigene Authentication Backends unterstützt, entschieden wir uns dafür, diesen Mechanismus auch für die Bereitstellung von OAuth 2.0 zu nutzen. Die Implementierung eines solchen Backends wird auf der Seite zur OAuth 2.0 App beschrieben.
ownCloud APIs
Die Implementierung der OCS Share API wird größtenteils durch die ownCloud App files_sharing
bereitgestellt. Daher mussten Änderungen auf Ebene von ownCloud Apps und im Speziellen bei der Authentifizierung der von ihnen definierten APIs durchgeführt werden. Wir folgten dem Vorschlag eines ownCloud Entwicklers, dafür einen Plugin Mechanismus zu implementieren, sodass Apps weitere Authentifizierungsmethoden bereitstellen können.
Implementierung
WebDAV
Um eigene Authentication Backends für die dav
App bekannt zu machen und gleichzeitig keine Abhängigkeit zu schaffen, nutzten wir Event Listener. Vor dem Start des WebDAV-Servers in der Datei appinfo/v1/webdav.php
lösten wir das authInit
-Event aus, wie folgendes Codebeispiel zeigt.
<?php
$event = new \OCP\SabrePluginEvent($server);
\OC::$server->getEventDispatcher()->dispatch('OCA\DAV\Connector\Sabre::authInit', $event);
Dadurch ist es ownCloud Apps möglich, das Event abzugreifen und vor dem Start des WebDAV-Servers weitere Authentication Backends hinzuzufügen. Wie dies in der OAuth 2.0 App implementiert wurde, beschreibt dessen Seite.
ownCloud APIs
Die Authentifizierung von API-Zugriffen in ownCloud geschieht in der Datei lib/private/legacy/api.php
, welche auf lib/private/User/Session.php
zurückgreift. Analog zu den bestehenden Funktionen tryTokenLogin
und tryBasicAuthLogin
fügten wir die Funktion tryAuthModuleLogin
hinzu. Ein AuthModule
kann dabei von ownCloud Apps bereitgestellt werden, indem eine Implementierung des Interfaces IAuthModule
(zu finden in der Datei lib/public/Authentication/IAuthModule.php
) registriert wird. Nachfolgendes Codebeispiel zeigt dieses Interface.
<?php
namespace OCP\Authentication;
use OCP\IRequest;
use OCP\IUser;
/**
* Interface IAuthModule
*
* @package OCP\Authentication
* @since 10.0.0
*/
interface IAuthModule {
/**
* Authenticates a request.
*
* @param IRequest $request The request.
*
* @return null|IUser The user if the request is authenticated, null otherwise.
* @since 10.0.0
*/
public function auth(IRequest $request);
/**
* Returns the user's password.
*
* @param IRequest $request The request.
*
* @return String The user's password.
* @since 10.0.0
*/
public function getUserPassword(IRequest $request);
}
Die Funktion auth
authentifiziert eine Anfrage und ermittelt (bei erfolgreicher Authentifizierung) den zugehörigen Nutzer. Zusätzlich gibt es noch die Funktion getUserPassword
, welche zu einer Anfrage das Passwort des Nutzers bestimmt. Sie wurde hinzugefügt, da die App encryption
in bestimmten Nutzungsszenarien auf das Passwort des Nutzers angewiesen ist.
In der Funktion tryAuthModuleLogin
der Klasse Session
werden nun alle von ownCloud Apps bereitgestellten Implementierungen von IAuthModule
geladen und zur Authentifizierung genutzt, wie im folgenden Codebeispiel zu sehen.
<?php
/**
* Tries to login with an AuthModule provided by an app
*
* @param IRequest $request The request
* @return bool True if request can be authenticated, false otherwise
* @throws Exception If the auth module could not be loaded
*/
public function tryAuthModuleLogin(IRequest $request) {
/** @var IAppManager $appManager */
$appManager = OC::$server->query('AppManager');
$allApps = $appManager->getInstalledApps();
foreach ($allApps as $appId) {
$info = $appManager->getAppInfo($appId);
if (isset($info['auth-modules'])) {
$authModules = $info['auth-modules'];
foreach ($authModules as $class) {
try {
if (!OC_App::isAppLoaded($appId)) {
OC_App::loadApp($appId);
}
/** @var IAuthModule $authModule */
$authModule = OC::$server->query($class);
if ($authModule instanceof IAuthModule) {
return $this->loginUser(
$authModule->auth($request),
$authModule->getUserPassword($request)
);
} else {
throw new Exception("Could not load the auth module $class");
}
} catch (QueryException $exc) {
throw new Exception("Could not load the auth module $class");
}
}
}
}
return false;
}
Hier wird in allen installierten Apps in der info.xml
nach dem Tag auth-modules
gesucht. Durch Nutzung von Registrierungen in der info.xml
war es möglich, Abhängigkeiten zu anderen Apps zu vermeiden. Falls die App ein AuthModule
implementiert und registriert hat, wird die registrierte Klasse geladen und für die Funktion loginUser
genutzt.
Die Funktion loginUser
ist analog zu der bestehenden Funktion loginWithToken
implementiert worden und nutzt die Rückgabewerte der beiden Funktionen aus dem AuthModule
, wie folgendes Codebeispiel zeigt.
/**
* Logs a user in
*
* @param IUser $user The user
* @param String $password The user's password
* @return boolean True if the user can be authenticated, false otherwise
* @throws LoginException if an app canceled the login process or the user is not enabled
*/
private function loginUser($user, $password) {
if (is_null($user)) {
return false;
}
$this->manager->emit('\OC\User', 'preLogin', [$user, $password]);
if (!$user->isEnabled()) {
$message = \OC::$server->getL10N('lib')->t('User disabled');
throw new LoginException($message);
}
$this->setUser($user);
$this->setLoginName($user->getDisplayName());
$this->manager->emit('\OC\User', 'postLogin', [$user, $password]);
if ($this->isLoggedIn()) {
$this->prepareUserLogin(false);
} else {
$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
throw new LoginException($message);
}
return true;
}
Die Implementierung und Registrierung eines AuthModule
s in der OAuth 2.0 App wird auf dessen Seite beschrieben.
Tests und Continuous Integration
Die Änderungen in den Pull Requests durchliefen die Tests zum ownCloud Core, die bei Jenkins und Travis ausgeführt wurden.