diff --git a/.github/workflows/smb-kerberos.yml b/.github/workflows/smb-kerberos.yml index 9f8309626c6cc..09e2c446915a2 100644 --- a/.github/workflows/smb-kerberos.yml +++ b/.github/workflows/smb-kerberos.yml @@ -74,7 +74,6 @@ jobs: with: repository: nextcloud/user_saml path: apps/user_saml - ref: event-dispatcher - name: Pull images run: | docker pull ghcr.io/icewind1991/samba-krb-test-dc @@ -100,3 +99,47 @@ jobs: FILEPATH=$(docker exec --user 33 apache ./occ log:file | grep "Log file:" | cut -d' ' -f3) echo "$FILEPATH:" docker exec --user 33 apache cat $FILEPATH + + smb-kerberos-database-tests: + runs-on: ubuntu-latest + + if: ${{ github.repository_owner != 'nextcloud-gmbh' }} + + name: smb-kerberos-sso + + steps: + - name: Checkout server + uses: actions/checkout@v3 + with: + submodules: true + - name: Checkout user_saml + uses: actions/checkout@v3 + with: + repository: nextcloud/user_saml + path: apps/user_saml + ref: event-dispatcher + - name: Pull images + run: | + docker pull ghcr.io/icewind1991/samba-krb-test-dc + docker pull ghcr.io/icewind1991/samba-krb-test-apache + docker pull ghcr.io/icewind1991/samba-krb-test-client + docker tag ghcr.io/icewind1991/samba-krb-test-dc icewind1991/samba-krb-test-dc + docker tag ghcr.io/icewind1991/samba-krb-test-apache icewind1991/samba-krb-test-apache + docker tag ghcr.io/icewind1991/samba-krb-test-client icewind1991/samba-krb-test-client + - name: Setup AD-DC + run: | + DC_IP=$(apps/files_external/tests/sso-setup/start-dc.sh) + apps/files_external/tests/sso-setup/start-apache.sh $DC_IP $PWD -v $PWD/apps/files_external/tests/sso-setup/apache-session.conf:/etc/apache2/sites-enabled/000-default.conf + echo "DC_IP=$DC_IP" >> $GITHUB_ENV + - name: Set up Nextcloud + run: | + apps/files_external/tests/sso-setup/setup-sso-nc.sh smb::kerberos_sso_database + - name: Test SSO + run: | + apps/files_external/tests/sso-setup/test-sso-smb-session.sh ${{ env.DC_IP }} + - name: Show logs + if: failure() + run: | + FILEPATH=$(docker exec --user 33 apache ./occ log:file | grep "Log file:" | cut -d' ' -f3) + echo "$FILEPATH:" + docker exec --user 33 apache cat $FILEPATH diff --git a/apps/files_external/composer/composer/autoload_classmap.php b/apps/files_external/composer/composer/autoload_classmap.php index a6061c949e735..48562ac8564df 100644 --- a/apps/files_external/composer/composer/autoload_classmap.php +++ b/apps/files_external/composer/composer/autoload_classmap.php @@ -54,6 +54,7 @@ 'OCA\\Files_External\\Lib\\Auth\\PublicKey\\RSAPrivateKey' => $baseDir . '/../lib/Lib/Auth/PublicKey/RSAPrivateKey.php', 'OCA\\Files_External\\Lib\\Auth\\SMB\\KerberosApacheAuth' => $baseDir . '/../lib/Lib/Auth/SMB/KerberosApacheAuth.php', 'OCA\\Files_External\\Lib\\Auth\\SMB\\KerberosAuth' => $baseDir . '/../lib/Lib/Auth/SMB/KerberosAuth.php', + 'OCA\\Files_External\\Lib\\Auth\\SMB\\KerberosSsoDatabase' => $baseDir . '/../lib/Lib/Auth/SMB/KerberosSsoDatabase.php', 'OCA\\Files_External\\Lib\\Auth\\SMB\\KerberosSsoSession' => $baseDir . '/../lib/Lib/Auth/SMB/KerberosSsoSession.php', 'OCA\\Files_External\\Lib\\Backend\\AmazonS3' => $baseDir . '/../lib/Lib/Backend/AmazonS3.php', 'OCA\\Files_External\\Lib\\Backend\\Backend' => $baseDir . '/../lib/Lib/Backend/Backend.php', diff --git a/apps/files_external/composer/composer/autoload_static.php b/apps/files_external/composer/composer/autoload_static.php index 532ee0050d97a..f77e20afa6b12 100644 --- a/apps/files_external/composer/composer/autoload_static.php +++ b/apps/files_external/composer/composer/autoload_static.php @@ -69,6 +69,7 @@ class ComposerStaticInitFiles_External 'OCA\\Files_External\\Lib\\Auth\\PublicKey\\RSAPrivateKey' => __DIR__ . '/..' . '/../lib/Lib/Auth/PublicKey/RSAPrivateKey.php', 'OCA\\Files_External\\Lib\\Auth\\SMB\\KerberosApacheAuth' => __DIR__ . '/..' . '/../lib/Lib/Auth/SMB/KerberosApacheAuth.php', 'OCA\\Files_External\\Lib\\Auth\\SMB\\KerberosAuth' => __DIR__ . '/..' . '/../lib/Lib/Auth/SMB/KerberosAuth.php', + 'OCA\\Files_External\\Lib\\Auth\\SMB\\KerberosSsoDatabase' => __DIR__ . '/..' . '/../lib/Lib/Auth/SMB/KerberosSsoDatabase.php', 'OCA\\Files_External\\Lib\\Auth\\SMB\\KerberosSsoSession' => __DIR__ . '/..' . '/../lib/Lib/Auth/SMB/KerberosSsoSession.php', 'OCA\\Files_External\\Lib\\Backend\\AmazonS3' => __DIR__ . '/..' . '/../lib/Lib/Backend/AmazonS3.php', 'OCA\\Files_External\\Lib\\Backend\\Backend' => __DIR__ . '/..' . '/../lib/Lib/Backend/Backend.php', diff --git a/apps/files_external/lib/Lib/Auth/SMB/KerberosSsoDatabase.php b/apps/files_external/lib/Lib/Auth/SMB/KerberosSsoDatabase.php new file mode 100644 index 0000000000000..5152b059a0415 --- /dev/null +++ b/apps/files_external/lib/Lib/Auth/SMB/KerberosSsoDatabase.php @@ -0,0 +1,73 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Files_External\Lib\Auth\SMB; + +use Icewind\SMB\KerberosTicket; +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\DefinitionParameter; +use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; +use OCP\IL10N; +use OCP\ISession; +use OCP\IUser; +use OCP\Security\ICredentialsManager; + +class KerberosSsoDatabase extends AuthMechanism { + private ICredentialsManager $credentialsManager; + + public function __construct(IL10N $l, ICredentialsManager $credentialsManager) { + $realm = new DefinitionParameter('default_realm', 'Default realm'); + $realm + ->setType(DefinitionParameter::VALUE_TEXT) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL) + ->setTooltip($l->t('Kerberos default realm, defaults to "WORKGROUP"')); + $this + ->setIdentifier('smb::kerberos_sso_database') + ->setScheme(self::SCHEME_SMB) + ->setText($l->t('Kerberos ticket SSO, save in database')) + ->addParameter($realm); + $this->credentialsManager = $credentialsManager; + } + + public function getTicket(?IUser $user): KerberosTicket { + if (!isset($user)) { + throw new InsufficientDataForMeaningfulAnswerException('No kerberos ticket saved'); + } + try { + $envTicket = KerberosTicket::fromEnv(); + } catch (\Exception $e) { + $envTicket = null; + } + if ($envTicket) { + $this->credentialsManager->store($user->getUID(), 'kerberos_ticket', base64_encode($envTicket->save())); + return $envTicket; + } + + $savedTicket = $this->credentialsManager->retrieve($user->getUID(), 'kerberos_ticket'); + if (!$savedTicket) { + throw new InsufficientDataForMeaningfulAnswerException('No kerberos ticket saved'); + } + return KerberosTicket::load(base64_decode($savedTicket)); + } +} diff --git a/apps/files_external/lib/Lib/Backend/SMB.php b/apps/files_external/lib/Lib/Backend/SMB.php index d04f8f3b71526..d9daa47903d05 100644 --- a/apps/files_external/lib/Lib/Backend/SMB.php +++ b/apps/files_external/lib/Lib/Backend/SMB.php @@ -34,6 +34,7 @@ use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\Auth\Password\Password; use OCA\Files_External\Lib\Auth\SMB\KerberosApacheAuth as KerberosApacheAuthMechanism; +use OCA\Files_External\Lib\Auth\SMB\KerberosSsoDatabase; use OCA\Files_External\Lib\Auth\SMB\KerberosSsoSession; use OCA\Files_External\Lib\DefinitionParameter; use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; @@ -91,6 +92,13 @@ public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = n case 'smb::kerberos': $smbAuth = new KerberosAuth(); break; + case 'smb::kerberos_sso_database': + if (!$auth instanceof KerberosSsoDatabase) { + throw new \InvalidArgumentException('invalid authentication backend'); + } + $smbAuth = new KerberosAuth(); + $smbAuth->setTicket($auth->getTicket($user)); + break; case 'smb::kerberos_sso_session': if (!$auth instanceof KerberosSsoSession) { throw new \InvalidArgumentException('invalid authentication backend'); diff --git a/apps/files_external/lib/Lib/TicketSaveMiddleware.php b/apps/files_external/lib/Lib/TicketSaveMiddleware.php index c33221e13fee2..1a3326870b7f8 100644 --- a/apps/files_external/lib/Lib/TicketSaveMiddleware.php +++ b/apps/files_external/lib/Lib/TicketSaveMiddleware.php @@ -25,47 +25,67 @@ use Icewind\SMB\KerberosTicket; use OCA\Files_External\Controller\UserGlobalStoragesController; +use OCA\Files_External\Lib\Auth\SMB\KerberosSsoDatabase; use OCA\Files_External\Lib\Auth\SMB\KerberosSsoSession; use OCA\Files_External\Service\UserGlobalStoragesService; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Middleware; use OCP\ISession; +use OCP\IUser; use OCP\IUserSession; +use OCP\Security\ICredentialsManager; class TicketSaveMiddleware extends Middleware { + const SAVE_SESSION = 1; + const SAVE_DB = 2; + private ISession $session; private IUserSession $userSession; private UserGlobalStoragesService $storagesService; + private ICredentialsManager $credentialsManager; public function __construct( ISession $session, + ICredentialsManager $credentialsManager, IUserSession $userSession, UserGlobalStoragesService $storagesService ) { $this->session = $session; + $this->credentialsManager = $credentialsManager; $this->userSession = $userSession; $this->storagesService = $storagesService; } public function afterController($controller, $methodName, Response $response) { + $user = $this->userSession->getUser(); + if (!$user) { + return $response; + } $ticket = KerberosTicket::fromEnv(); - if ($ticket && $ticket->isValid() && $this->needToSaveTicket()) { - $this->session->set('kerberos_ticket', base64_encode($ticket->save())); + if ($ticket && $ticket->isValid()) { + $save = $this->needToSaveTicket($user); + if ($save & self::SAVE_SESSION) { + $this->session->set('kerberos_ticket', base64_encode($ticket->save())); + } + if ($save & self::SAVE_DB) { + $this->credentialsManager->store($user->getUID(), 'kerberos_ticket', base64_encode($ticket->save())); + } } return $response; } - private function needToSaveTicket(): bool { - $user = $this->userSession->getUser(); - if (!$user) { - return false; - } + private function needToSaveTicket(IUser $user): int { + $save = 0; $storages = $this->storagesService->getAllStoragesForUser($user); foreach ($storages as $storage) { - if ($storage->getAuthMechanism() instanceof KerberosSsoSession) { - return true; + $auth = $storage->getAuthMechanism(); + if ($auth instanceof KerberosSsoSession) { + $save = $save | self::SAVE_SESSION; + } + if ($auth instanceof KerberosSsoDatabase) { + $save = $save | self::SAVE_DB; } } - return false; + return $save; } }