diff --git a/.github/workflows/smb-kerberos.yml b/.github/workflows/smb-kerberos.yml
index c10d4f59d2940..1dd88fe02d8c3 100644
--- a/.github/workflows/smb-kerberos.yml
+++ b/.github/workflows/smb-kerberos.yml
@@ -18,8 +18,6 @@ jobs:
if: ${{ github.repository_owner != 'nextcloud-gmbh' }}
- name: smb-kerberos-sso
-
steps:
- name: Checkout server
uses: actions/checkout@v3
@@ -30,7 +28,7 @@ jobs:
with:
repository: nextcloud/user_saml
path: apps/user_saml
- ref: stable27
+ ref: stable-5.2
- name: Pull images
run: |
docker pull ghcr.io/icewind1991/samba-krb-test-dc
@@ -56,3 +54,89 @@ 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-session-tests:
+ runs-on: ubuntu-latest
+
+ if: ${{ github.repository_owner != 'nextcloud-gmbh' }}
+
+ 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: stable-5.2
+ - 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)
+ sleep 1
+ 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_session
+ - 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
+
+ smb-kerberos-database-tests:
+ runs-on: ubuntu-latest
+
+ if: ${{ github.repository_owner != 'nextcloud-gmbh' }}
+
+ 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: stable-5.2
+ - 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)
+ sleep 1
+ 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/3rdparty/composer.json b/apps/files_external/3rdparty/composer.json
index 348e482c2c35c..0da6cfdf11efb 100644
--- a/apps/files_external/3rdparty/composer.json
+++ b/apps/files_external/3rdparty/composer.json
@@ -8,7 +8,7 @@
"classmap-authoritative": true
},
"require": {
- "icewind/smb": "3.5.4",
+ "icewind/smb": "3.6.0",
"icewind/streams": "0.7.7"
}
}
diff --git a/apps/files_external/3rdparty/composer.lock b/apps/files_external/3rdparty/composer.lock
index 281fdef3e234d..2be44f816314d 100644
--- a/apps/files_external/3rdparty/composer.lock
+++ b/apps/files_external/3rdparty/composer.lock
@@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "1419e286d2372dfbce44dd73ddbab2ff",
+ "content-hash": "dc5d93a9b45922aeec3823baf867acdc",
"packages": [
{
"name": "icewind/smb",
- "version": "v3.5.4",
+ "version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/SMB.git",
- "reference": "76995aa11c14e39bccd0f2370ed63b2f8f623a6d"
+ "reference": "e0e86b16640f5892dd00408ed50ad18357dac6c1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/icewind1991/SMB/zipball/76995aa11c14e39bccd0f2370ed63b2f8f623a6d",
- "reference": "76995aa11c14e39bccd0f2370ed63b2f8f623a6d",
+ "url": "https://api.github.com/repos/icewind1991/SMB/zipball/e0e86b16640f5892dd00408ed50ad18357dac6c1",
+ "reference": "e0e86b16640f5892dd00408ed50ad18357dac6c1",
"shasum": ""
},
"require": {
@@ -49,9 +49,9 @@
"description": "php wrapper for smbclient and libsmbclient-php",
"support": {
"issues": "https://github.com/icewind1991/SMB/issues",
- "source": "https://github.com/icewind1991/SMB/tree/v3.5.4"
+ "source": "https://github.com/icewind1991/SMB/tree/v3.6.0"
},
- "time": "2022-05-30T15:18:19+00:00"
+ "time": "2023-08-10T13:17:39+00:00"
},
{
"name": "icewind/streams",
diff --git a/apps/files_external/3rdparty/composer/autoload_classmap.php b/apps/files_external/3rdparty/composer/autoload_classmap.php
index 66b472bd42219..8ed82b8402335 100644
--- a/apps/files_external/3rdparty/composer/autoload_classmap.php
+++ b/apps/files_external/3rdparty/composer/autoload_classmap.php
@@ -32,6 +32,7 @@
'Icewind\\SMB\\Exception\\InvalidPathException' => $vendorDir . '/icewind/smb/src/Exception/InvalidPathException.php',
'Icewind\\SMB\\Exception\\InvalidRequestException' => $vendorDir . '/icewind/smb/src/Exception/InvalidRequestException.php',
'Icewind\\SMB\\Exception\\InvalidResourceException' => $vendorDir . '/icewind/smb/src/Exception/InvalidResourceException.php',
+ 'Icewind\\SMB\\Exception\\InvalidTicket' => $vendorDir . '/icewind/smb/src/Exception/InvalidTicket.php',
'Icewind\\SMB\\Exception\\InvalidTypeException' => $vendorDir . '/icewind/smb/src/Exception/InvalidTypeException.php',
'Icewind\\SMB\\Exception\\NoLoginServerException' => $vendorDir . '/icewind/smb/src/Exception/NoLoginServerException.php',
'Icewind\\SMB\\Exception\\NoRouteToHostException' => $vendorDir . '/icewind/smb/src/Exception/NoRouteToHostException.php',
@@ -50,6 +51,7 @@
'Icewind\\SMB\\ITimeZoneProvider' => $vendorDir . '/icewind/smb/src/ITimeZoneProvider.php',
'Icewind\\SMB\\KerberosApacheAuth' => $vendorDir . '/icewind/smb/src/KerberosApacheAuth.php',
'Icewind\\SMB\\KerberosAuth' => $vendorDir . '/icewind/smb/src/KerberosAuth.php',
+ 'Icewind\\SMB\\KerberosTicket' => $vendorDir . '/icewind/smb/src/KerberosTicket.php',
'Icewind\\SMB\\Native\\NativeFileInfo' => $vendorDir . '/icewind/smb/src/Native/NativeFileInfo.php',
'Icewind\\SMB\\Native\\NativeReadStream' => $vendorDir . '/icewind/smb/src/Native/NativeReadStream.php',
'Icewind\\SMB\\Native\\NativeServer' => $vendorDir . '/icewind/smb/src/Native/NativeServer.php',
diff --git a/apps/files_external/3rdparty/composer/autoload_static.php b/apps/files_external/3rdparty/composer/autoload_static.php
index 1d309dcd6f1ca..75b1adaa17b55 100644
--- a/apps/files_external/3rdparty/composer/autoload_static.php
+++ b/apps/files_external/3rdparty/composer/autoload_static.php
@@ -52,6 +52,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
'Icewind\\SMB\\Exception\\InvalidPathException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidPathException.php',
'Icewind\\SMB\\Exception\\InvalidRequestException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidRequestException.php',
'Icewind\\SMB\\Exception\\InvalidResourceException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidResourceException.php',
+ 'Icewind\\SMB\\Exception\\InvalidTicket' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidTicket.php',
'Icewind\\SMB\\Exception\\InvalidTypeException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidTypeException.php',
'Icewind\\SMB\\Exception\\NoLoginServerException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NoLoginServerException.php',
'Icewind\\SMB\\Exception\\NoRouteToHostException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NoRouteToHostException.php',
@@ -70,6 +71,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3
'Icewind\\SMB\\ITimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/ITimeZoneProvider.php',
'Icewind\\SMB\\KerberosApacheAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosApacheAuth.php',
'Icewind\\SMB\\KerberosAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosAuth.php',
+ 'Icewind\\SMB\\KerberosTicket' => __DIR__ . '/..' . '/icewind/smb/src/KerberosTicket.php',
'Icewind\\SMB\\Native\\NativeFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeFileInfo.php',
'Icewind\\SMB\\Native\\NativeReadStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeReadStream.php',
'Icewind\\SMB\\Native\\NativeServer' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeServer.php',
diff --git a/apps/files_external/3rdparty/composer/installed.json b/apps/files_external/3rdparty/composer/installed.json
index 9b66e6497158c..40f08ff4be6cd 100644
--- a/apps/files_external/3rdparty/composer/installed.json
+++ b/apps/files_external/3rdparty/composer/installed.json
@@ -2,17 +2,17 @@
"packages": [
{
"name": "icewind/smb",
- "version": "v3.5.4",
- "version_normalized": "3.5.4.0",
+ "version": "v3.6.0",
+ "version_normalized": "3.6.0.0",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/SMB.git",
- "reference": "76995aa11c14e39bccd0f2370ed63b2f8f623a6d"
+ "reference": "e0e86b16640f5892dd00408ed50ad18357dac6c1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/icewind1991/SMB/zipball/76995aa11c14e39bccd0f2370ed63b2f8f623a6d",
- "reference": "76995aa11c14e39bccd0f2370ed63b2f8f623a6d",
+ "url": "https://api.github.com/repos/icewind1991/SMB/zipball/e0e86b16640f5892dd00408ed50ad18357dac6c1",
+ "reference": "e0e86b16640f5892dd00408ed50ad18357dac6c1",
"shasum": ""
},
"require": {
@@ -25,7 +25,7 @@
"phpunit/phpunit": "^8.5|^9.3.8",
"psalm/phar": "^4.3"
},
- "time": "2022-05-30T15:18:19+00:00",
+ "time": "2023-08-10T13:17:39+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -46,7 +46,7 @@
"description": "php wrapper for smbclient and libsmbclient-php",
"support": {
"issues": "https://github.com/icewind1991/SMB/issues",
- "source": "https://github.com/icewind1991/SMB/tree/v3.5.4"
+ "source": "https://github.com/icewind1991/SMB/tree/v3.6.0"
},
"install-path": "../icewind/smb"
},
diff --git a/apps/files_external/3rdparty/composer/installed.php b/apps/files_external/3rdparty/composer/installed.php
index 65684203f3f4f..f70a30b8ca781 100644
--- a/apps/files_external/3rdparty/composer/installed.php
+++ b/apps/files_external/3rdparty/composer/installed.php
@@ -3,7 +3,7 @@
'name' => 'files_external/3rdparty',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => '979f4033ca1cd0be7f255f028d4cc637a216440d',
+ 'reference' => '733d6b54097417b3f49673ac0491aebdb46648fd',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
@@ -13,16 +13,16 @@
'files_external/3rdparty' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => '979f4033ca1cd0be7f255f028d4cc637a216440d',
+ 'reference' => '733d6b54097417b3f49673ac0491aebdb46648fd',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
'dev_requirement' => false,
),
'icewind/smb' => array(
- 'pretty_version' => 'v3.5.4',
- 'version' => '3.5.4.0',
- 'reference' => '76995aa11c14e39bccd0f2370ed63b2f8f623a6d',
+ 'pretty_version' => 'v3.6.0',
+ 'version' => '3.6.0.0',
+ 'reference' => 'e0e86b16640f5892dd00408ed50ad18357dac6c1',
'type' => 'library',
'install_path' => __DIR__ . '/../icewind/smb',
'aliases' => array(),
diff --git a/apps/files_external/3rdparty/icewind/smb/README.md b/apps/files_external/3rdparty/icewind/smb/README.md
index fec1faefbadb6..ed1d54e323c37 100644
--- a/apps/files_external/3rdparty/icewind/smb/README.md
+++ b/apps/files_external/3rdparty/icewind/smb/README.md
@@ -65,19 +65,20 @@ $server = $serverFactory->createServer('localhost', $auth);
By re-using a client ticket you can create a single sign-on setup where the user authenticates against
the web service using kerberos. And the web server can forward that ticket to the smb server, allowing it
-to act on the behalf of the user without requiring the user to enter his passord.
+to act on the behalf of the user without requiring the user to enter his password.
The setup for such a system is fairly involved and requires roughly the following this
- The web server is authenticated against kerberos with a machine account
- Delegation is enabled for the web server's machine account
-- Apache is setup to perform kerberos authentication and save the ticket in it's environment
+- The web server is setup to perform kerberos authentication and save the ticket in it's environment
- Php has the krb5 extension installed
- The client authenticates using a ticket with forwarding enabled
```php
$serverFactory = new ServerFactory();
-$auth = new KerberosApacheAuth();
+$auth = new KerberosAuth();
+$auth->setTicket(KerberosTicket::fromEnv());
$server = $serverFactory->createServer('localhost', $auth);
```
diff --git a/apps/files_external/3rdparty/icewind/smb/example-sso-kerberos.php b/apps/files_external/3rdparty/icewind/smb/example-sso-kerberos.php
new file mode 100644
index 0000000000000..ca1055bf37499
--- /dev/null
+++ b/apps/files_external/3rdparty/icewind/smb/example-sso-kerberos.php
@@ -0,0 +1,26 @@
+setTicket(KerberosTicket::fromEnv());
+$serverFactory = new \Icewind\SMB\ServerFactory();
+
+$server = $serverFactory->createServer($host, $auth);
+
+$share = $server->getShare($share);
+
+$files = $share->dir('/');
+foreach ($files as $file) {
+ echo $file->getName() . "\n";
+}
diff --git a/apps/files_external/3rdparty/icewind/smb/src/Exception/InvalidTicket.php b/apps/files_external/3rdparty/icewind/smb/src/Exception/InvalidTicket.php
new file mode 100644
index 0000000000000..9021346aba6be
--- /dev/null
+++ b/apps/files_external/3rdparty/icewind/smb/src/Exception/InvalidTicket.php
@@ -0,0 +1,28 @@
+
+ *
+ * @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 Icewind\SMB\Exception;
+
+class InvalidTicket extends Exception {
+
+}
\ No newline at end of file
diff --git a/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php b/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php
index c49918be114a6..c8de5555b31a8 100644
--- a/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php
+++ b/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php
@@ -23,114 +23,40 @@
use Icewind\SMB\Exception\DependencyException;
use Icewind\SMB\Exception\Exception;
+use Icewind\SMB\Exception\InvalidTicket;
/**
* Use existing kerberos ticket to authenticate and reuse the apache ticket cache (mod_auth_kerb)
+ *
+ * @deprecated Use `KerberosAuth` with `$auth->setTicket(KerberosTicket::fromEnv())` instead
*/
class KerberosApacheAuth extends KerberosAuth implements IAuth {
- /** @var string */
- private $ticketPath = "";
-
- /** @var bool */
- private $init = false;
-
- /** @var string|false */
- private $ticketName;
-
- public function __construct() {
- $this->ticketName = getenv("KRB5CCNAME");
+ public function getTicket(): KerberosTicket {
+ if ($this->ticket === null) {
+ $ticket = KerberosTicket::fromEnv();
+ if ($ticket === null) {
+ throw new InvalidTicket("No ticket found in environment");
+ }
+ $this->ticket = $ticket;
+ }
+ return $this->ticket;
}
-
/**
* Copy the ticket to a temporary location and use that ticket for authentication
*
* @return void
*/
public function copyTicket(): void {
- if (!$this->checkTicket()) {
- return;
- }
- $krb5 = new \KRB5CCache();
- $krb5->open($this->ticketName);
- $tmpFilename = tempnam("/tmp", "krb5cc_php_");
- $tmpCacheFile = "FILE:" . $tmpFilename;
- $krb5->save($tmpCacheFile);
- $this->ticketPath = $tmpFilename;
- $this->ticketName = $tmpCacheFile;
- }
-
- /**
- * Pass the ticket to smbclient by memory instead of path
- *
- * @return void
- */
- public function passTicketFromMemory(): void {
- if (!$this->checkTicket()) {
- return;
- }
- $krb5 = new \KRB5CCache();
- $krb5->open($this->ticketName);
- $this->ticketName = (string)$krb5->getName();
+ $this->ticket = KerberosTicket::load($this->getTicket()->save());
}
/**
* Check if a valid kerberos ticket is present
*
* @return bool
- * @psalm-assert-if-true string $this->ticketName
*/
public function checkTicket(): bool {
- //read apache kerberos ticket cache
- if (!$this->ticketName) {
- return false;
- }
-
- $krb5 = new \KRB5CCache();
- $krb5->open($this->ticketName);
- /** @psalm-suppress MixedArgument */
- return count($krb5->getEntries()) > 0;
- }
-
- private function init(): void {
- if ($this->init) {
- return;
- }
- $this->init = true;
- // inspired by https://git.typo3.org/TYPO3CMS/Extensions/fal_cifs.git
-
- if (!extension_loaded("krb5")) {
- // https://pecl.php.net/package/krb5
- throw new DependencyException('Ensure php-krb5 is installed.');
- }
-
- //read apache kerberos ticket cache
- if (!$this->checkTicket()) {
- throw new Exception('No kerberos ticket cache environment variable (KRB5CCNAME) found.');
- }
-
- // note that even if the ticketname is the value we got from `getenv("KRB5CCNAME")` we still need to set the env variable ourselves
- // this is because `getenv` also reads the variables passed from the SAPI (apache-php) and we need to set the variable in the OS's env
- putenv("KRB5CCNAME=" . $this->ticketName);
- }
-
- public function getExtraCommandLineArguments(): string {
- $this->init();
- return parent::getExtraCommandLineArguments();
- }
-
- public function setExtraSmbClientOptions($smbClientState): void {
- $this->init();
- try {
- parent::setExtraSmbClientOptions($smbClientState);
- } catch (Exception $e) {
- // suppress
- }
- }
-
- public function __destruct() {
- if (!empty($this->ticketPath) && file_exists($this->ticketPath) && is_file($this->ticketPath)) {
- unlink($this->ticketPath);
- }
+ return $this->getTicket()->isValid();
}
}
diff --git a/apps/files_external/3rdparty/icewind/smb/src/KerberosAuth.php b/apps/files_external/3rdparty/icewind/smb/src/KerberosAuth.php
index 68fb74ff9abdc..d827cfcaf3228 100644
--- a/apps/files_external/3rdparty/icewind/smb/src/KerberosAuth.php
+++ b/apps/files_external/3rdparty/icewind/smb/src/KerberosAuth.php
@@ -27,6 +27,17 @@
* Use existing kerberos ticket to authenticate
*/
class KerberosAuth implements IAuth {
+ /** @var ?KerberosTicket */
+ protected $ticket = null;
+
+ public function getTicket(): ?KerberosTicket {
+ return $this->ticket;
+ }
+
+ public function setTicket(?KerberosTicket $ticket): void {
+ $this->ticket = $ticket;
+ }
+
public function getUsername(): ?string {
return 'dummy';
}
@@ -39,11 +50,25 @@ public function getPassword(): ?string {
return null;
}
+ private function setEnv():void {
+ $ticket = $this->getTicket();
+ if ($ticket) {
+ $ticket->validate();
+
+ // note that even if the ticket name is the value we got from `getenv("KRB5CCNAME")` we still need to set the env variable ourselves
+ // this is because `getenv` also reads the variables passed from the SAPI (apache-php) and we need to set the variable in the OS's env
+ putenv("KRB5CCNAME=" . $ticket->getCacheName());
+ }
+ }
+
public function getExtraCommandLineArguments(): string {
+ $this->setEnv();
return '-k';
}
public function setExtraSmbClientOptions($smbClientState): void {
+ $this->setEnv();
+
$success = (bool)smbclient_option_set($smbClientState, SMBCLIENT_OPT_USE_KERBEROS, true);
$success = $success && smbclient_option_set($smbClientState, SMBCLIENT_OPT_FALLBACK_AFTER_KERBEROS, false);
diff --git a/apps/files_external/3rdparty/icewind/smb/src/KerberosTicket.php b/apps/files_external/3rdparty/icewind/smb/src/KerberosTicket.php
new file mode 100644
index 0000000000000..ea074501e684c
--- /dev/null
+++ b/apps/files_external/3rdparty/icewind/smb/src/KerberosTicket.php
@@ -0,0 +1,100 @@
+
+ *
+ * @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 Icewind\SMB;
+
+use Icewind\SMB\Exception\InvalidTicket;
+use KRB5CCache;
+
+class KerberosTicket {
+ /** @var KRB5CCache */
+ private $krb5;
+ /** @var string */
+ private $cacheName;
+
+ public function __construct(KRB5CCache $krb5, string $cacheName) {
+ $this->krb5 = $krb5;
+ $this->cacheName = $cacheName;
+ }
+
+ public function getCacheName(): string {
+ return $this->cacheName;
+ }
+
+ public function getName(): string{
+ return $this->krb5->getName();
+ }
+
+ public function isValid(): bool {
+ return count($this->krb5->getEntries()) > 0;
+ }
+
+ public function validate(): void {
+ if (!$this->isValid()) {
+ throw new InvalidTicket("No kerberos ticket found.");
+ }
+ }
+
+ /**
+ * Load the ticket from the cache specified by the KRB5CCNAME variable.
+ *
+ * @return KerberosTicket|null
+ */
+ public static function fromEnv(): ?KerberosTicket {
+ $ticketName = getenv("KRB5CCNAME");
+ if (!$ticketName) {
+ return null;
+ }
+ $krb5 = new KRB5CCache();
+ $krb5->open($ticketName);
+ return new KerberosTicket($krb5, $ticketName);
+ }
+
+ public static function load(string $ticket): KerberosTicket {
+ $tmpFilename = tempnam(sys_get_temp_dir(), "krb5cc_php_");
+ file_put_contents($tmpFilename, $ticket);
+ register_shutdown_function(function () use ($tmpFilename) {
+ if (file_exists($tmpFilename)) {
+ unlink($tmpFilename);
+ }
+ });
+
+ $ticketName = "FILE:" . $tmpFilename;
+ $krb5 = new KRB5CCache();
+ $krb5->open($ticketName);
+ return new KerberosTicket($krb5, $ticketName);
+ }
+
+ public function save(): string {
+ if (substr($this->cacheName, 0, 5) === 'FILE:') {
+ $ticket = file_get_contents(substr($this->cacheName, 5));
+ } else {
+ $tmpFilename = tempnam(sys_get_temp_dir(), "krb5cc_php_");
+ $tmpCacheFile = "FILE:" . $tmpFilename;
+ $this->krb5->save($tmpCacheFile);
+ $ticket = file_get_contents($tmpFilename);
+ unlink($tmpFilename);
+ }
+ return $ticket;
+ }
+}
\ No newline at end of file
diff --git a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php
index 088e6f733d123..acbfef141983e 100644
--- a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php
+++ b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php
@@ -8,6 +8,7 @@
namespace Icewind\SMB\Native;
use Icewind\SMB\Exception\AlreadyExistsException;
+use Icewind\SMB\Exception\ConnectionException;
use Icewind\SMB\Exception\ConnectionRefusedException;
use Icewind\SMB\Exception\ConnectionResetException;
use Icewind\SMB\Exception\Exception;
@@ -32,9 +33,6 @@ class NativeState {
/** @var resource|null */
protected $state = null;
- /** @var bool */
- protected $handlerSet = false;
-
/** @var bool */
protected $connected = false;
@@ -67,7 +65,9 @@ class NativeState {
];
protected function handleError(?string $path): void {
- /** @var int $error */
+ if (!$this->state) {
+ return;
+ }
$error = smbclient_state_errno($this->state);
if ($error === 0) {
return;
@@ -120,7 +120,6 @@ public function init(IAuth $auth, IOptions $options) {
// __deconstruct() of KerberosAuth should not caled too soon
$this->auth = $auth;
- /** @var bool $result */
$result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword());
$this->testResult($result, '');
@@ -133,6 +132,9 @@ public function init(IAuth $auth, IOptions $options) {
* @return resource
*/
public function opendir(string $uri) {
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
/** @var resource $result */
$result = @smbclient_opendir($this->state, $uri);
@@ -146,6 +148,9 @@ public function opendir(string $uri) {
* @return array{"type": string, "comment": string, "name": string}|false
*/
public function readdir($dir, string $path) {
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
/** @var array{"type": string, "comment": string, "name": string}|false $result */
$result = @smbclient_readdir($this->state, $dir);
@@ -159,8 +164,10 @@ public function readdir($dir, string $path) {
* @return bool
*/
public function closedir($dir, string $path): bool {
- /** @var bool $result */
- $result = smbclient_closedir($this->state, $dir);
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
+ $result = @smbclient_closedir($this->state, $dir);
$this->testResult($result, $path);
return $result;
@@ -172,7 +179,9 @@ public function closedir($dir, string $path): bool {
* @return bool
*/
public function rename(string $old, string $new): bool {
- /** @var bool $result */
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
$result = @smbclient_rename($this->state, $old, $this->state, $new);
$this->testResult($result, $new);
@@ -184,7 +193,9 @@ public function rename(string $old, string $new): bool {
* @return bool
*/
public function unlink(string $uri): bool {
- /** @var bool $result */
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
$result = @smbclient_unlink($this->state, $uri);
$this->testResult($result, $uri);
@@ -197,7 +208,9 @@ public function unlink(string $uri): bool {
* @return bool
*/
public function mkdir(string $uri, int $mask = 0777): bool {
- /** @var bool $result */
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
$result = @smbclient_mkdir($this->state, $uri, $mask);
$this->testResult($result, $uri);
@@ -209,7 +222,9 @@ public function mkdir(string $uri, int $mask = 0777): bool {
* @return bool
*/
public function rmdir(string $uri): bool {
- /** @var bool $result */
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
$result = @smbclient_rmdir($this->state, $uri);
$this->testResult($result, $uri);
@@ -221,6 +236,9 @@ public function rmdir(string $uri): bool {
* @return array{"mtime": int, "size": int, "mode": int}
*/
public function stat(string $uri): array {
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
/** @var array{"mtime": int, "size": int, "mode": int} $result */
$result = @smbclient_stat($this->state, $uri);
@@ -234,6 +252,9 @@ public function stat(string $uri): array {
* @return array{"mtime": int, "size": int, "mode": int}
*/
public function fstat($file, string $path): array {
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
/** @var array{"mtime": int, "size": int, "mode": int} $result */
$result = @smbclient_fstat($this->state, $file);
@@ -248,6 +269,9 @@ public function fstat($file, string $path): array {
* @return resource
*/
public function open(string $uri, string $mode, int $mask = 0666) {
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
/** @var resource $result */
$result = @smbclient_open($this->state, $uri, $mode, $mask);
@@ -261,6 +285,9 @@ public function open(string $uri, string $mode, int $mask = 0666) {
* @return resource
*/
public function create(string $uri, int $mask = 0666) {
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
/** @var resource $result */
$result = @smbclient_creat($this->state, $uri, $mask);
@@ -275,6 +302,9 @@ public function create(string $uri, int $mask = 0666) {
* @return string
*/
public function read($file, int $bytes, string $path): string {
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
/** @var string $result */
$result = @smbclient_read($this->state, $file, $bytes);
@@ -290,10 +320,19 @@ public function read($file, int $bytes, string $path): string {
* @return int
*/
public function write($file, string $data, string $path, ?int $length = null): int {
- /** @var int $result */
- $result = @smbclient_write($this->state, $file, $data, $length);
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
+ if ($length) {
+ $result = @smbclient_write($this->state, $file, $data, $length);
+ } else {
+ $result = @smbclient_write($this->state, $file, $data);
+ }
$this->testResult($result, $path);
+ if ($result === false) {
+ return 0;
+ }
return $result;
}
@@ -302,10 +341,18 @@ public function write($file, string $data, string $path, ?int $length = null): i
* @param int $offset
* @param int $whence SEEK_SET | SEEK_CUR | SEEK_END
* @param string|null $path
- * @return int|false new file offset as measured from the start of the file on success.
+ *
+ * @return false|int new file offset as measured from the start of the file on success.
*/
public function lseek($file, int $offset, int $whence = SEEK_SET, string $path = null) {
- /** @var int|false $result */
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
+ // psalm doesn't think int|false == int|false for some reason, so we do a needless annotation to help it out
+ /**
+ * @psalm-suppress UnnecessaryVarAnnotation
+ * @var int|false $result
+ */
$result = @smbclient_lseek($this->state, $file, $offset, $whence);
$this->testResult($result, $path);
@@ -319,7 +366,9 @@ public function lseek($file, int $offset, int $whence = SEEK_SET, string $path =
* @return bool
*/
public function ftruncate($file, int $size, string $path): bool {
- /** @var bool $result */
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
$result = @smbclient_ftruncate($this->state, $file, $size);
$this->testResult($result, $path);
@@ -332,7 +381,9 @@ public function ftruncate($file, int $size, string $path): bool {
* @return bool
*/
public function close($file, string $path): bool {
- /** @var bool $result */
+ if (!$this->state) {
+ return false;
+ }
$result = @smbclient_close($this->state, $file);
$this->testResult($result, $path);
@@ -345,6 +396,9 @@ public function close($file, string $path): bool {
* @return string
*/
public function getxattr(string $uri, string $key) {
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
/** @var string $result */
$result = @smbclient_getxattr($this->state, $uri, $key);
@@ -360,6 +414,9 @@ public function getxattr(string $uri, string $key) {
* @return bool
*/
public function setxattr(string $uri, string $key, string $value, int $flags = 0) {
+ if (!$this->state) {
+ throw new ConnectionException("Not connected");
+ }
/** @var bool $result */
$result = @smbclient_setxattr($this->state, $uri, $key, $value, $flags);
@@ -368,7 +425,7 @@ public function setxattr(string $uri, string $key, string $value, int $flags = 0
}
public function __destruct() {
- if ($this->connected) {
+ if ($this->connected && $this->state) {
if (smbclient_state_free($this->state) === false) {
throw new Exception("Failed to free smb state");
}
diff --git a/apps/files_external/composer/composer/autoload_classmap.php b/apps/files_external/composer/composer/autoload_classmap.php
index c6037ea9ded82..48562ac8564df 100644
--- a/apps/files_external/composer/composer/autoload_classmap.php
+++ b/apps/files_external/composer/composer/autoload_classmap.php
@@ -54,6 +54,8 @@
'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',
'OCA\\Files_External\\Lib\\Backend\\DAV' => $baseDir . '/../lib/Lib/Backend/DAV.php',
@@ -94,6 +96,7 @@
'OCA\\Files_External\\Lib\\Storage\\SMB' => $baseDir . '/../lib/Lib/Storage/SMB.php',
'OCA\\Files_External\\Lib\\Storage\\StreamWrapper' => $baseDir . '/../lib/Lib/Storage/StreamWrapper.php',
'OCA\\Files_External\\Lib\\Storage\\Swift' => $baseDir . '/../lib/Lib/Storage/Swift.php',
+ 'OCA\\Files_External\\Lib\\TicketSaveMiddleware' => $baseDir . '/../lib/Lib/TicketSaveMiddleware.php',
'OCA\\Files_External\\Lib\\VisibilityTrait' => $baseDir . '/../lib/Lib/VisibilityTrait.php',
'OCA\\Files_External\\Listener\\GroupDeletedListener' => $baseDir . '/../lib/Listener/GroupDeletedListener.php',
'OCA\\Files_External\\Listener\\StorePasswordListener' => $baseDir . '/../lib/Listener/StorePasswordListener.php',
diff --git a/apps/files_external/composer/composer/autoload_static.php b/apps/files_external/composer/composer/autoload_static.php
index 26091fb32b3e0..f77e20afa6b12 100644
--- a/apps/files_external/composer/composer/autoload_static.php
+++ b/apps/files_external/composer/composer/autoload_static.php
@@ -69,6 +69,8 @@ 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',
'OCA\\Files_External\\Lib\\Backend\\DAV' => __DIR__ . '/..' . '/../lib/Lib/Backend/DAV.php',
@@ -109,6 +111,7 @@ class ComposerStaticInitFiles_External
'OCA\\Files_External\\Lib\\Storage\\SMB' => __DIR__ . '/..' . '/../lib/Lib/Storage/SMB.php',
'OCA\\Files_External\\Lib\\Storage\\StreamWrapper' => __DIR__ . '/..' . '/../lib/Lib/Storage/StreamWrapper.php',
'OCA\\Files_External\\Lib\\Storage\\Swift' => __DIR__ . '/..' . '/../lib/Lib/Storage/Swift.php',
+ 'OCA\\Files_External\\Lib\\TicketSaveMiddleware' => __DIR__ . '/..' . '/../lib/Lib/TicketSaveMiddleware.php',
'OCA\\Files_External\\Lib\\VisibilityTrait' => __DIR__ . '/..' . '/../lib/Lib/VisibilityTrait.php',
'OCA\\Files_External\\Listener\\GroupDeletedListener' => __DIR__ . '/..' . '/../lib/Listener/GroupDeletedListener.php',
'OCA\\Files_External\\Listener\\StorePasswordListener' => __DIR__ . '/..' . '/../lib/Listener/StorePasswordListener.php',
diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php
index 6f8018746b34b..b29e96b1c94a4 100644
--- a/apps/files_external/lib/AppInfo/Application.php
+++ b/apps/files_external/lib/AppInfo/Application.php
@@ -49,6 +49,8 @@
use OCA\Files_External\Lib\Auth\PublicKey\RSAPrivateKey;
use OCA\Files_External\Lib\Auth\SMB\KerberosApacheAuth;
use OCA\Files_External\Lib\Auth\SMB\KerberosAuth;
+use OCA\Files_External\Lib\Auth\SMB\KerberosSsoDatabase;
+use OCA\Files_External\Lib\Auth\SMB\KerberosSsoSession;
use OCA\Files_External\Lib\Backend\AmazonS3;
use OCA\Files_External\Lib\Backend\DAV;
use OCA\Files_External\Lib\Backend\FTP;
@@ -61,6 +63,7 @@
use OCA\Files_External\Lib\Backend\Swift;
use OCA\Files_External\Lib\Config\IAuthMechanismProvider;
use OCA\Files_External\Lib\Config\IBackendProvider;
+use OCA\Files_External\Lib\TicketSaveMiddleware;
use OCA\Files_External\Listener\GroupDeletedListener;
use OCA\Files_External\Listener\UserDeletedListener;
use OCA\Files_External\Service\BackendService;
@@ -91,6 +94,7 @@ public function __construct(array $urlParams = []) {
public function register(IRegistrationContext $context): void {
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
$context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class);
+ $context->registerMiddleware(TicketSaveMiddleware::class, true);
}
public function boot(IBootContext $context): void {
@@ -182,6 +186,8 @@ public function getAuthMechanisms() {
$container->get(AccessKey::class),
$container->get(KerberosAuth::class),
$container->get(KerberosApacheAuth::class),
+ $container->get(KerberosSsoSession::class),
+ $container->get(KerberosSsoDatabase::class),
];
}
}
diff --git a/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php b/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php
index 17492280275b2..1581b286f4b3b 100644
--- a/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php
+++ b/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php
@@ -42,7 +42,7 @@ public function __construct(IL10N $l, IStore $credentialsStore) {
$this
->setIdentifier('smb::kerberosapache')
->setScheme(self::SCHEME_SMB)
- ->setText($l->t('Kerberos ticket Apache mode'))
+ ->setText($l->t('Kerberos ticket SSO'))
->addParameter($realm);
$this->credentialsStore = $credentialsStore;
}
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/Auth/SMB/KerberosSsoSession.php b/apps/files_external/lib/Lib/Auth/SMB/KerberosSsoSession.php
new file mode 100644
index 0000000000000..76ad9bcdeb419
--- /dev/null
+++ b/apps/files_external/lib/Lib/Auth/SMB/KerberosSsoSession.php
@@ -0,0 +1,68 @@
+
+ *
+ * @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;
+
+class KerberosSsoSession extends AuthMechanism {
+ private ISession $session;
+
+ public function __construct(IL10N $l, ISession $session) {
+ $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_session')
+ ->setScheme(self::SCHEME_SMB)
+ ->setText($l->t('Kerberos ticket SSO, save in session'))
+ ->addParameter($realm);
+ $this->session = $session;
+ }
+
+ public function getTicket(): KerberosTicket {
+ try {
+ $envTicket = KerberosTicket::fromEnv();
+ } catch (\Exception $e) {
+ $envTicket = null;
+ }
+ if ($envTicket) {
+ $this->session->set('kerberos_ticket', base64_encode($envTicket->save()));
+ return $envTicket;
+ }
+
+ $savedTicket = $this->session->get('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 bf73c5b40f844..d9daa47903d05 100644
--- a/apps/files_external/lib/Lib/Backend/SMB.php
+++ b/apps/files_external/lib/Lib/Backend/SMB.php
@@ -30,9 +30,12 @@
use Icewind\SMB\BasicAuth;
use Icewind\SMB\KerberosApacheAuth;
use Icewind\SMB\KerberosAuth;
+use Icewind\SMB\KerberosTicket;
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;
use OCA\Files_External\Lib\LegacyDependencyCheckPolyfill;
@@ -89,16 +92,32 @@ 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');
+ }
+ $smbAuth = new KerberosAuth();
+ $smbAuth->setTicket($auth->getTicket());
+ break;
case 'smb::kerberosapache':
if (!$auth instanceof KerberosApacheAuthMechanism) {
throw new \InvalidArgumentException('invalid authentication backend');
}
- $credentialsStore = $auth->getCredentialsStore();
- $kerbAuth = new KerberosApacheAuth();
+ $ticket = KerberosTicket::fromEnv();
// check if a kerberos ticket is available, else fallback to session credentials
- if ($kerbAuth->checkTicket()) {
+ if ($ticket && $ticket->isValid()) {
+ $kerbAuth = new KerberosAuth();
+ $kerbAuth->setTicket($ticket);
$smbAuth = $kerbAuth;
} else {
+ $credentialsStore = $auth->getCredentialsStore();
try {
$credentials = $credentialsStore->getLoginCredentials();
$user = $credentials->getLoginName();
diff --git a/apps/files_external/lib/Lib/TicketSaveMiddleware.php b/apps/files_external/lib/Lib/TicketSaveMiddleware.php
new file mode 100644
index 0000000000000..1a3326870b7f8
--- /dev/null
+++ b/apps/files_external/lib/Lib/TicketSaveMiddleware.php
@@ -0,0 +1,91 @@
+
+ *
+ * @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;
+
+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()) {
+ $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(IUser $user): int {
+ $save = 0;
+ $storages = $this->storagesService->getAllStoragesForUser($user);
+ foreach ($storages as $storage) {
+ $auth = $storage->getAuthMechanism();
+ if ($auth instanceof KerberosSsoSession) {
+ $save = $save | self::SAVE_SESSION;
+ }
+ if ($auth instanceof KerberosSsoDatabase) {
+ $save = $save | self::SAVE_DB;
+ }
+ }
+ return $save;
+ }
+}
diff --git a/apps/files_external/tests/sso-setup/apache-session.conf b/apps/files_external/tests/sso-setup/apache-session.conf
new file mode 100644
index 0000000000000..d401b07710ed6
--- /dev/null
+++ b/apps/files_external/tests/sso-setup/apache-session.conf
@@ -0,0 +1,31 @@
+
+ ServerAdmin webmaster@localhost
+ DocumentRoot /var/www/html
+
+
+ AuthType Kerberos
+ AuthName "Kerberos authenticated intranet"
+ KrbAuthRealms DOMAIN.TEST
+ KrbServiceName HTTP/httpd.domain.test
+ Krb5Keytab /shared/httpd.keytab
+ KrbMethodNegotiate On
+ KrbMethodK5Passwd On
+ KrbSaveCredentials On
+ require valid-user
+
+
+
+ AuthType Kerberos
+ AuthName "Kerberos authenticated intranet"
+ KrbAuthRealms DOMAIN.TEST
+ KrbServiceName HTTP/httpd.domain.test
+ Krb5Keytab /shared/httpd.keytab
+ KrbMethodNegotiate On
+ KrbMethodK5Passwd On
+ KrbSaveCredentials On
+ require valid-user
+
+
+ ErrorLog /shared/apache-error.log
+ CustomLog /shared/apache-access.log combined
+
diff --git a/apps/files_external/tests/sso-setup/run.sh b/apps/files_external/tests/sso-setup/run.sh
new file mode 100755
index 0000000000000..af7a72467a2d8
--- /dev/null
+++ b/apps/files_external/tests/sso-setup/run.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -e
+
+SCRIPT_DIR="${0%/*}"
+
+DC_IP=$("$SCRIPT_DIR"/start-dc.sh)
+"$SCRIPT_DIR"/start-apache.sh "$DC_IP" "$PWD" -v "$PWD/$SCRIPT_DIR"/apache-session.conf:/etc/apache2/sites-enabled/000-default.conf
+"$SCRIPT_DIR"/setup-sso-nc.sh smb::kerberos_sso_session
+
+"$SCRIPT_DIR"/test-sso-smb-session.sh "$DC_IP"
diff --git a/apps/files_external/tests/sso-setup/setup-sso-nc.sh b/apps/files_external/tests/sso-setup/setup-sso-nc.sh
index 60cc51ff68d36..af73a48732d23 100755
--- a/apps/files_external/tests/sso-setup/setup-sso-nc.sh
+++ b/apps/files_external/tests/sso-setup/setup-sso-nc.sh
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -e
+AUTH=${1:-"smb::kerberosapache"}
+
docker exec --user 33 apache ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password
docker exec --user 33 apache ./occ config:system:set trusted_domains 1 --value 'httpd.domain.test'
@@ -15,7 +17,7 @@ docker exec -e OC_PASS=test --user 33 apache ./occ user:add 'testuser@DOMAIN.TES
# setup external storage
docker exec --user 33 apache ./occ app:enable files_external --force
-docker exec --user 33 apache ./occ files_external:create smb smb smb::kerberosapache
+docker exec --user 33 apache ./occ files_external:create smb smb "$AUTH"
docker exec --user 33 apache ./occ files_external:config 1 host krb.domain.test
docker exec --user 33 apache ./occ files_external:config 1 share netlogon
docker exec --user 33 apache ./occ files_external:list
diff --git a/apps/files_external/tests/sso-setup/start-apache.sh b/apps/files_external/tests/sso-setup/start-apache.sh
index b483c73fa4ff9..b947c3fe052ab 100755
--- a/apps/files_external/tests/sso-setup/start-apache.sh
+++ b/apps/files_external/tests/sso-setup/start-apache.sh
@@ -5,12 +5,18 @@ SCRIPT_DIR="${0%/*}"
docker rm -f apache 2>/dev/null > /dev/null
-docker run -d --name apache -v $2:/var/www/html -v /var/www/html/data -v /var/www/html/config -v /var/www/html/extra-apps -v /tmp/shared:/shared --dns $1 --hostname httpd.domain.test icewind1991/samba-krb-test-apache 1>&2
+DC_IP="$1"
+DIR="$2"
+shift 2
+
+# shellcheck disable=SC2068
+docker run -d --name apache -v "$DIR":/var/www/html -v /var/www/html/data -v /var/www/html/config -v /var/www/html/extra-apps -v /tmp/shared:/shared \
+ --add-host host.docker.internal:host-gateway --dns "$DC_IP" --hostname httpd.domain.test $@ icewind1991/samba-krb-test-apache 1>&2
APACHE_IP=$(docker inspect apache --format '{{.NetworkSettings.IPAddress}}')
docker exec apache chown 33 /var/www/html/config /var/www/html/data /var/www/html/extra-apps
docker cp "$SCRIPT_DIR/apps.config.php" apache:/var/www/html/config/apps.config.php
# add the dns record for apache
-docker exec dc samba-tool dns add krb.domain.test domain.test httpd A $APACHE_IP -U administrator --password=passwOrd1 1>&2
+docker exec dc samba-tool dns add krb.domain.test domain.test httpd A "$APACHE_IP" -U administrator --password=passwOrd1 1>&2
-echo $APACHE_IP
+echo "$APACHE_IP"
diff --git a/apps/files_external/tests/sso-setup/start-dc.sh b/apps/files_external/tests/sso-setup/start-dc.sh
index df8b02318dfe1..821a72bfb67b4 100755
--- a/apps/files_external/tests/sso-setup/start-dc.sh
+++ b/apps/files_external/tests/sso-setup/start-dc.sh
@@ -2,14 +2,14 @@
set -e
function getContainerHealth {
- docker inspect --format "{{.State.Health.Status}}" $1
+ docker inspect --format "{{.State.Health.Status}}" "$1"
}
function waitContainer {
- while STATUS=$(getContainerHealth $1); [ $STATUS != "healthy" ]; do
- if [ $STATUS == "unhealthy" ]; then
+ while STATUS=$(getContainerHealth "$1"); [ "$STATUS" != "healthy" ]; do
+ if [ "$STATUS" == "unhealthy" ]; then
echo "Failed!" 1>&2
- exit -1
+ exit 1
fi
printf . 1>&2
lf=$'\n'
@@ -27,4 +27,6 @@ docker run -dit --name dc -v /tmp/shared:/shared --hostname krb.domain.test --ca
waitContainer dc
+sleep 5
+
docker inspect dc --format '{{.NetworkSettings.IPAddress}}'
diff --git a/apps/files_external/tests/sso-setup/test-sso-smb-session.sh b/apps/files_external/tests/sso-setup/test-sso-smb-session.sh
new file mode 100755
index 0000000000000..d39b4dea815b6
--- /dev/null
+++ b/apps/files_external/tests/sso-setup/test-sso-smb-session.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+set -e
+
+DC_IP="$1"
+SCRIPT_DIR="${0%/*}"
+
+echo -n "Checking that we can authenticate using kerberos: "
+LOGIN_CONTENT=$("$SCRIPT_DIR/client-cmd.sh" "$DC_IP" curl -i -s -c /shared/cookie -i -s --negotiate -u testuser@DOMAIN.TEST: --delegation always 'http://httpd.domain.test/index.php/apps/user_saml/saml/login?originalUrl=success&XDEBUG_SESSION_START=1')
+if [[ "$LOGIN_CONTENT" =~ "Location: success" ]]; then
+ echo "✔️"
+else
+ echo "❌"
+ exit 1
+fi
+
+"$SCRIPT_DIR/client-cmd.sh" "$DC_IP" curl -s -b /shared/cookie -c /shared/cookie --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/index.php
+
+echo -n "Getting test with session file: "
+CONTENT=$("$SCRIPT_DIR/client-cmd.sh" "$DC_IP" curl -s -b /shared/cookie 'http://httpd.domain.test/remote.php/webdav/smb/test.txt?XDEBUG_SESSION_START=1')
+CONTENT=$(echo "$CONTENT" | head -n 1 | tr -d '[:space:]')
+
+if [[ $CONTENT == "testfile" ]]; then
+ echo "✔️"
+else
+ echo "❌"
+ exit 1
+fi
diff --git a/apps/files_external/tests/sso-setup/test-sso-smb.sh b/apps/files_external/tests/sso-setup/test-sso-smb.sh
index b0f0a2c7af92f..d21df094e8df8 100755
--- a/apps/files_external/tests/sso-setup/test-sso-smb.sh
+++ b/apps/files_external/tests/sso-setup/test-sso-smb.sh
@@ -5,7 +5,7 @@ DC_IP="$1"
SCRIPT_DIR="${0%/*}"
echo -n "Checking that we can authenticate using kerberos: "
-LOGIN_CONTENT=$("$SCRIPT_DIR/client-cmd.sh" $DC_IP curl -i -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/index.php/apps/user_saml/saml/login?originalUrl=success)
+LOGIN_CONTENT=$("$SCRIPT_DIR/client-cmd.sh" "$DC_IP" curl -i -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/index.php/apps/user_saml/saml/login?originalUrl=success)
if [[ "$LOGIN_CONTENT" =~ "Location: success" ]]; then
echo "✔️"
else
@@ -13,8 +13,8 @@ else
exit 1
fi
echo -n "Getting test file: "
-CONTENT=$("$SCRIPT_DIR/client-cmd.sh" $DC_IP curl -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/remote.php/webdav/smb/test.txt)
-CONTENT=$(echo $CONTENT | head -n 1 | tr -d '[:space:]')
+CONTENT=$("$SCRIPT_DIR/client-cmd.sh" "$DC_IP" curl -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/remote.php/webdav/smb/test.txt)
+CONTENT=$(echo "$CONTENT" | head -n 1 | tr -d '[:space:]')
if [[ $CONTENT == "testfile" ]]; then
echo "✔️"