diff options
25 files changed, 633 insertions, 126 deletions
diff --git a/.github/workflows/smb-kerberos.yml b/.github/workflows/smb-kerberos.yml new file mode 100644 index 00000000000..2875a7f4fbd --- /dev/null +++ b/.github/workflows/smb-kerberos.yml @@ -0,0 +1,78 @@ +name: Samba Kerberos SSO +on: + push: + branches: + - master + - stable* + paths: + - 'apps/files_external/**' + pull_request: + paths: + - 'apps/files_external/**' + +jobs: + smb-kerberos-tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php-versions: ['7.4', '8.0'] + + name: php${{ matrix.php-versions }}-${{ matrix.ftpd }} + + steps: + - name: Checkout server + uses: actions/checkout@v2 + with: + submodules: true + - name: Pull images + run: | + docker pull icewind1991/samba-krb-test-dc + docker pull icewind1991/samba-krb-test-apache + docker pull icewind1991/samba-krb-test-client + - name: Setup AD-DC + run: | + mkdir data + sudo chown -R 33 data apps config + apps/files_external/tests/setup-krb.sh + - name: Set up Nextcloud + run: | + 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' + + # setup user_saml + docker exec --user 33 apache ./occ app:enable user_saml --force + docker exec --user 33 apache ./occ config:app:set user_saml type --value 'environment-variable' + docker exec --user 33 apache ./occ config:app:set user_saml general-uid_mapping --value REMOTE_USER + + # 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: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 + - name: Test SSO + run: | + mkdir cookies + chmod 0777 cookies + + DC_IP=$(docker inspect dc --format '{{.NetworkSettings.IPAddress}}') + docker run --rm --name client -v $PWD/cookies:/cookies -v /tmp/shared:/shared --dns $DC_IP --hostname client.domain.test icewind1991/samba-krb-test-client \ + curl -c /cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/index.php/apps/user_saml/saml/login + CONTENT=$(docker run --rm --name client -v $PWD/cookies:/cookies -v /tmp/shared:/shared --dns $DC_IP --hostname client.domain.test icewind1991/samba-krb-test-client \ + curl -b /cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/remote.php/webdav/smb/test.txt) + echo $CONTENT + CONTENT=$(echo $CONTENT | tr -d '[:space:]') + [[ $CONTENT == "testfile" ]] + + + smb-kerberos-summary: + runs-on: ubuntu-latest + needs: smb-kerberos-tests + + if: always() + + steps: + - name: Summary status + run: if ${{ needs.smb-kerberos-tests.result != 'success' }}; then exit 1; fi diff --git a/apps/files_external/3rdparty/.gitignore b/apps/files_external/3rdparty/.gitignore index 651eb60572d..e787d39fca6 100644 --- a/apps/files_external/3rdparty/.gitignore +++ b/apps/files_external/3rdparty/.gitignore @@ -5,6 +5,8 @@ icewind/smb/install_libsmbclient.sh icewind/smb/Makefile icewind/smb/.travis.yml icewind/smb/.scrutinizer.yml +icewind/smb/example-apache-kerberos.php +icewind/smb/codecov.yml icewind/streams/tests .github .php_cs* diff --git a/apps/files_external/3rdparty/composer.json b/apps/files_external/3rdparty/composer.json index d8854aa976a..e343521add3 100644 --- a/apps/files_external/3rdparty/composer.json +++ b/apps/files_external/3rdparty/composer.json @@ -9,6 +9,6 @@ }, "require": { "icewind/streams": "0.7.4", - "icewind/smb": "3.4.1" + "icewind/smb": "3.5.2" } } diff --git a/apps/files_external/3rdparty/composer.lock b/apps/files_external/3rdparty/composer.lock index 05de684a017..918dd7da08d 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": "0ffc772b2aaaaffe52decb8d13361976", + "content-hash": "524c99fd87297e01d004eb5a4e53b04c", "packages": [ { "name": "icewind/smb", - "version": "v3.4.1", + "version": "v3.5.2", "source": { "type": "git", "url": "https://github.com/icewind1991/SMB.git", - "reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3" + "reference": "0a425bd21acf7ae112b135dca34640e1b1a825c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/SMB/zipball/9dba42ab2a3990de29e18cc62b0a8270aceb74e3", - "reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/0a425bd21acf7ae112b135dca34640e1b1a825c3", + "reference": "0a425bd21acf7ae112b135dca34640e1b1a825c3", "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.4.1" + "source": "https://github.com/icewind1991/SMB/tree/v3.5.2" }, - "time": "2021-04-19T13:53:08+00:00" + "time": "2022-01-20T14:51:51+00:00" }, { "name": "icewind/streams", @@ -107,5 +107,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.2.0" } diff --git a/apps/files_external/3rdparty/composer/ClassLoader.php b/apps/files_external/3rdparty/composer/ClassLoader.php index 6d0c3f2d001..afef3fa2ad8 100644 --- a/apps/files_external/3rdparty/composer/ClassLoader.php +++ b/apps/files_external/3rdparty/composer/ClassLoader.php @@ -42,30 +42,75 @@ namespace Composer\Autoload; */ class ClassLoader { + /** @var ?string */ private $vendorDir; // PSR-4 + /** + * @var array[] + * @psalm-var array<string, array<string, int>> + */ private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array<string, array<int, string>> + */ private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array<string, string> + */ private $fallbackDirsPsr4 = array(); // PSR-0 + /** + * @var array[] + * @psalm-var array<string, array<string, string[]>> + */ private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array<string, string> + */ private $fallbackDirsPsr0 = array(); + /** @var bool */ private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array<string, string> + */ private $classMap = array(); + + /** @var bool */ private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array<string, bool> + */ private $missingClasses = array(); + + /** @var ?string */ private $apcuPrefix; + /** + * @var self[] + */ private static $registeredLoaders = array(); + /** + * @param ?string $vendorDir + */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; } + /** + * @return string[] + */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { @@ -75,28 +120,47 @@ class ClassLoader return array(); } + /** + * @return array[] + * @psalm-return array<string, array<int, string>> + */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } + /** + * @return array[] + * @psalm-return array<string, string> + */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } + /** + * @return array[] + * @psalm-return array<string, string> + */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } + /** + * @return string[] Array of classname => path + * @psalm-return array<string, string> + */ public function getClassMap() { return $this->classMap; } /** - * @param array $classMap Class to filename map + * @param string[] $classMap Class to filename map + * @psalm-param array<string, string> $classMap + * + * @return void */ public function addClassMap(array $classMap) { @@ -111,9 +175,11 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void */ public function add($prefix, $paths, $prepend = false) { @@ -156,11 +222,13 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException + * + * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { @@ -204,8 +272,10 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 base directories + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void */ public function set($prefix, $paths) { @@ -220,10 +290,12 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException + * + * @return void */ public function setPsr4($prefix, $paths) { @@ -243,6 +315,8 @@ class ClassLoader * Turns on searching the include path for class files. * * @param bool $useIncludePath + * + * @return void */ public function setUseIncludePath($useIncludePath) { @@ -265,6 +339,8 @@ class ClassLoader * that have not been registered with the class map. * * @param bool $classMapAuthoritative + * + * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { @@ -285,6 +361,8 @@ class ClassLoader * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix + * + * @return void */ public function setApcuPrefix($apcuPrefix) { @@ -305,6 +383,8 @@ class ClassLoader * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void */ public function register($prepend = false) { @@ -324,6 +404,8 @@ class ClassLoader /** * Unregisters this instance as an autoloader. + * + * @return void */ public function unregister() { @@ -403,6 +485,11 @@ class ClassLoader return self::$registeredLoaders; } + /** + * @param string $class + * @param string $ext + * @return string|false + */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup @@ -474,6 +561,10 @@ class ClassLoader * Scope isolated include. * * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + * @private */ function includeFile($file) { diff --git a/apps/files_external/3rdparty/composer/InstalledVersions.php b/apps/files_external/3rdparty/composer/InstalledVersions.php index b3a4e1611e6..d50e0c9fcc4 100644 --- a/apps/files_external/3rdparty/composer/InstalledVersions.php +++ b/apps/files_external/3rdparty/composer/InstalledVersions.php @@ -20,12 +20,25 @@ use Composer\Semver\VersionParser; * * See also https://getcomposer.org/doc/07-runtime.md#installed-versions * - * To require it's presence, you can require `composer-runtime-api ^2.0` + * To require its presence, you can require `composer-runtime-api ^2.0` */ class InstalledVersions { + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null + */ private static $installed; + + /** + * @var bool|null + */ private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> + */ private static $installedByVendor = array(); /** @@ -228,7 +241,7 @@ class InstalledVersions /** * @return array - * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string} + * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} */ public static function getRootPackage() { @@ -242,7 +255,7 @@ class InstalledVersions * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] - * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} + * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} */ public static function getRawData() { @@ -265,7 +278,7 @@ class InstalledVersions * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] - * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}> + * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> */ public static function getAllRawData() { @@ -288,7 +301,7 @@ class InstalledVersions * @param array[] $data A vendor/composer/installed.php data set * @return void * - * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data + * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data */ public static function reload($data) { @@ -298,7 +311,7 @@ class InstalledVersions /** * @return array[] - * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}> + * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> */ private static function getInstalled() { diff --git a/apps/files_external/3rdparty/composer/autoload_classmap.php b/apps/files_external/3rdparty/composer/autoload_classmap.php index d0f82994f29..17b94af0e99 100644 --- a/apps/files_external/3rdparty/composer/autoload_classmap.php +++ b/apps/files_external/3rdparty/composer/autoload_classmap.php @@ -48,6 +48,7 @@ return array( 'Icewind\\SMB\\IShare' => $vendorDir . '/icewind/smb/src/IShare.php', 'Icewind\\SMB\\ISystem' => $vendorDir . '/icewind/smb/src/ISystem.php', '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\\Native\\NativeFileInfo' => $vendorDir . '/icewind/smb/src/Native/NativeFileInfo.php', 'Icewind\\SMB\\Native\\NativeReadStream' => $vendorDir . '/icewind/smb/src/Native/NativeReadStream.php', diff --git a/apps/files_external/3rdparty/composer/autoload_static.php b/apps/files_external/3rdparty/composer/autoload_static.php index 899982f2a67..1d309dcd6f1 100644 --- a/apps/files_external/3rdparty/composer/autoload_static.php +++ b/apps/files_external/3rdparty/composer/autoload_static.php @@ -68,6 +68,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3 'Icewind\\SMB\\IShare' => __DIR__ . '/..' . '/icewind/smb/src/IShare.php', 'Icewind\\SMB\\ISystem' => __DIR__ . '/..' . '/icewind/smb/src/ISystem.php', '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\\Native\\NativeFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeFileInfo.php', 'Icewind\\SMB\\Native\\NativeReadStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeReadStream.php', diff --git a/apps/files_external/3rdparty/composer/installed.json b/apps/files_external/3rdparty/composer/installed.json index 7405962c70c..2c9b1c087df 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.4.1", - "version_normalized": "3.4.1.0", + "version": "v3.5.2", + "version_normalized": "3.5.2.0", "source": { "type": "git", "url": "https://github.com/icewind1991/SMB.git", - "reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3" + "reference": "0a425bd21acf7ae112b135dca34640e1b1a825c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/SMB/zipball/9dba42ab2a3990de29e18cc62b0a8270aceb74e3", - "reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/0a425bd21acf7ae112b135dca34640e1b1a825c3", + "reference": "0a425bd21acf7ae112b135dca34640e1b1a825c3", "shasum": "" }, "require": { @@ -25,7 +25,7 @@ "phpunit/phpunit": "^8.5|^9.3.8", "psalm/phar": "^4.3" }, - "time": "2021-04-19T13:53:08+00:00", + "time": "2022-01-20T14:51:51+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.4.1" + "source": "https://github.com/icewind1991/SMB/tree/v3.5.2" }, "install-path": "../icewind/smb" }, diff --git a/apps/files_external/3rdparty/composer/installed.php b/apps/files_external/3rdparty/composer/installed.php index 7dfd1c420f7..255420e1003 100644 --- a/apps/files_external/3rdparty/composer/installed.php +++ b/apps/files_external/3rdparty/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => '70483a16a3a232758979bb6fa363629b5a16b6a4', + 'reference' => '0bed61f949bc7a8c69cd154919e78b704e28c99e', 'name' => 'files_external/3rdparty', 'dev' => true, ), @@ -16,16 +16,16 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => '70483a16a3a232758979bb6fa363629b5a16b6a4', + 'reference' => '0bed61f949bc7a8c69cd154919e78b704e28c99e', 'dev_requirement' => false, ), 'icewind/smb' => array( - 'pretty_version' => 'v3.4.1', - 'version' => '3.4.1.0', + 'pretty_version' => 'v3.5.2', + 'version' => '3.5.2.0', 'type' => 'library', 'install_path' => __DIR__ . '/../icewind/smb', 'aliases' => array(), - 'reference' => '9dba42ab2a3990de29e18cc62b0a8270aceb74e3', + 'reference' => '0a425bd21acf7ae112b135dca34640e1b1a825c3', 'dev_requirement' => false, ), 'icewind/streams' => array( diff --git a/apps/files_external/3rdparty/icewind/smb/README.md b/apps/files_external/3rdparty/icewind/smb/README.md index 272c4ebedcd..fec1faefbad 100644 --- a/apps/files_external/3rdparty/icewind/smb/README.md +++ b/apps/files_external/3rdparty/icewind/smb/README.md @@ -44,13 +44,42 @@ $server = $serverFactory->createServer('localhost', $auth); ### Using kerberos authentication ### +There are two ways of using kerberos to authenticate against the smb server: + +- Using a ticket from the php server +- Re-using a ticket send by the client + +### Using a server ticket + +Using a server ticket allows the web server to authenticate against the smb server using an existing machine account. + +The ticket needs to be available in the environment of the php process. + ```php $serverFactory = new ServerFactory(); $auth = new KerberosAuth(); $server = $serverFactory->createServer('localhost', $auth); ``` -Note that this requires a valid kerberos ticket to already be available for php +### Re-using a client ticket + +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. + +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 +- Php has the krb5 extension installed +- The client authenticates using a ticket with forwarding enabled + +```php +$serverFactory = new ServerFactory(); +$auth = new KerberosApacheAuth(); +$server = $serverFactory->createServer('localhost', $auth); +``` ### Upload a file ### diff --git a/apps/files_external/3rdparty/icewind/smb/src/IShare.php b/apps/files_external/3rdparty/icewind/smb/src/IShare.php index 6ac6e0d2d15..40213b93a99 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/IShare.php +++ b/apps/files_external/3rdparty/icewind/smb/src/IShare.php @@ -45,7 +45,7 @@ interface IShare { public function put(string $source, string $target): bool; /** - * Open a readable stream top a remote file + * Open a readable stream to a remote file * * @param string $source * @return resource a read only stream with the contents of the remote file diff --git a/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php b/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php new file mode 100644 index 00000000000..c49918be114 --- /dev/null +++ b/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php @@ -0,0 +1,136 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Icewind\SMB; + +use Icewind\SMB\Exception\DependencyException; +use Icewind\SMB\Exception\Exception; + +/** + * Use existing kerberos ticket to authenticate and reuse the apache ticket cache (mod_auth_kerb) + */ +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"); + } + + + /** + * 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(); + } + + /** + * 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); + } + } +} diff --git a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeFileInfo.php b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeFileInfo.php index 539bb728426..85fb0274ac1 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeFileInfo.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeFileInfo.php @@ -99,7 +99,7 @@ class NativeFileInfo implements IFileInfo { public function isDirectory(): bool { $mode = $this->getMode(); if ($mode > 0x1000) { - return (bool)($mode & 0x4000); // 0x4000: unix directory flag + return ($mode & 0x4000 && !($mode & 0x8000)); // 0x4000: unix directory flag shares bits with 0xC000: socket } else { return (bool)($mode & IFileInfo::MODE_DIRECTORY); } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeShare.php b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeShare.php index 03ec501b830..8c4eab2a60f 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeShare.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeShare.php @@ -267,14 +267,14 @@ class NativeShare extends AbstractShare { * Open a writeable stream to a remote file * Note: This method will truncate the file to 0bytes first * - * @param string $source + * @param string $target * @return resource a writeable stream * * @throws NotFoundException * @throws InvalidTypeException */ - public function write(string $source) { - $url = $this->buildUrl($source); + public function write(string $target) { + $url = $this->buildUrl($target); $handle = $this->getState()->create($url); return NativeWriteStream::wrap($this->getState(), $handle, 'w', $url); } @@ -282,14 +282,14 @@ class NativeShare extends AbstractShare { /** * Open a writeable stream and set the cursor to the end of the stream * - * @param string $source + * @param string $target * @return resource a writeable stream * * @throws NotFoundException * @throws InvalidTypeException */ - public function append(string $source) { - $url = $this->buildUrl($source); + public function append(string $target) { + $url = $this->buildUrl($target); $handle = $this->getState()->open($url, "a+"); return NativeWriteStream::wrap($this->getState(), $handle, "a", $url); } 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 e1a13ce3e72..088e6f733d1 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php @@ -38,6 +38,14 @@ class NativeState { /** @var bool */ protected $connected = false; + /** + * sync the garbage collection cycle + * __deconstruct() of KerberosAuth should not called too soon + * + * @var IAuth|null $auth + */ + protected $auth = null; + // see error.h const EXCEPTION_MAP = [ 1 => ForbiddenException::class, @@ -107,6 +115,11 @@ class NativeState { } $auth->setExtraSmbClientOptions($this->state); + + // sync the garbage collection cycle + // __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()); diff --git a/apps/files_external/3rdparty/icewind/smb/src/System.php b/apps/files_external/3rdparty/icewind/smb/src/System.php index 919907477ab..d3475e7a5cb 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/System.php +++ b/apps/files_external/3rdparty/icewind/smb/src/System.php @@ -62,7 +62,7 @@ class System implements ISystem { $result = null; $output = []; exec("which $binary 2>&1", $output, $result); - $this->paths[$binary] = $result === 0 ? trim(implode('', $output)) : null; + $this->paths[$binary] = $result === 0 && isset($output[0]) ? (string)$output[0] : null; } return $this->paths[$binary]; } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Connection.php b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Connection.php index 31b72b05d97..cc73ac1ad14 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Connection.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Connection.php @@ -47,7 +47,7 @@ class Connection extends RawConnection { public function clearTillPrompt(): void { $this->write(''); do { - $promptLine = $this->readLine(); + $promptLine = $this->readTillPrompt(); if ($promptLine === false) { break; } @@ -56,13 +56,12 @@ class Connection extends RawConnection { if ($this->write('') === false) { throw new ConnectionRefusedException(); } - $this->readLine(); + $this->readTillPrompt(); } /** * get all unprocessed output from smbclient until the next prompt * - * @param (callable(string):bool)|null $callback (optional) callback to call for every line read * @return string[] * @throws AuthenticationException * @throws ConnectException @@ -71,42 +70,22 @@ class Connection extends RawConnection { * @throws NoLoginServerException * @throws AccessDeniedException */ - public function read(callable $callback = null): array { + public function read(): array { if (!$this->isValid()) { throw new ConnectionException('Connection not valid'); } - $promptLine = $this->readLine(); //first line is prompt - if ($promptLine === false) { - $this->unknownError($promptLine); - } - $this->parser->checkConnectionError($promptLine); - - $output = []; - if (!$this->isPrompt($promptLine)) { - $line = $promptLine; - } else { - $line = $this->readLine(); - } - if ($line === false) { - $this->unknownError($promptLine); - } - while ($line !== false && !$this->isPrompt($line)) { //next prompt functions as delimiter - if (is_callable($callback)) { - $result = $callback($line); - if ($result === false) { // allow the callback to close the connection for infinite running commands - $this->close(true); - break; - } - } else { - $output[] = $line; - } - $line = $this->readLine(); + $output = $this->readTillPrompt(); + if ($output === false) { + $this->unknownError(false); } + $output = explode("\n", $output); + // last line contains the prompt + array_pop($output); return $output; } private function isPrompt(string $line): bool { - return mb_substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER; + return substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER; } /** @@ -132,6 +111,6 @@ class Connection extends RawConnection { // ignore any errors while trying to send the close command, the process might already be dead @$this->write('close' . PHP_EOL); } - parent::close($terminate); + $this->close_process($terminate); } } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/NotifyHandler.php b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/NotifyHandler.php index 18451f4daa6..ecb5bb1e3c1 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/NotifyHandler.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/NotifyHandler.php @@ -65,16 +65,20 @@ class NotifyHandler implements INotifyHandler { */ public function listen(callable $callback): void { if ($this->listening) { - $this->connection->read(function (string $line) use ($callback): bool { + while (true) { + $line = $this->connection->readLine(); + if ($line === false) { + break; + } $this->checkForError($line); $change = $this->parseChangeLine($line); if ($change) { $result = $callback($change); - return $result === false ? false : true; - } else { - return true; + if ($result === false) { + break; + } } - }); + }; } } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/RawConnection.php b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/RawConnection.php index 26a17cc584b..4aec674c3da 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/RawConnection.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/RawConnection.php @@ -71,7 +71,8 @@ class RawConnection { setlocale(LC_ALL, Server::LOCALE); $env = array_merge($this->env, [ - 'CLI_FORCE_INTERACTIVE' => 'y', // Needed or the prompt isn't displayed!! + 'CLI_FORCE_INTERACTIVE' => 'y', // Make sure the prompt is displayed + 'CLI_NO_READLINE' => 1, // Not all distros build smbclient with readline, disable it to get consistent behaviour 'LC_ALL' => Server::LOCALE, 'LANG' => Server::LOCALE, 'COLUMNS' => 8192 // prevent smbclient from line-wrapping it's output @@ -91,7 +92,7 @@ class RawConnection { public function isValid(): bool { if (is_resource($this->process)) { $status = proc_get_status($this->process); - return (bool)$status['running']; + return $status['running']; } else { return false; } @@ -110,12 +111,29 @@ class RawConnection { } /** + * read output till the next prompt + * + * @return string|false + */ + public function readTillPrompt() { + $output = ""; + do { + $chunk = $this->readLine('\> '); + if ($chunk === false) { + return false; + } + $output .= $chunk; + } while (strlen($chunk) == 4096 && strpos($chunk, "smb:") === false); + return $output; + } + + /** * read a line of output * * @return string|false */ - public function readLine() { - return stream_get_line($this->getOutputStream(), 4086, "\n"); + public function readLine(string $end = "\n") { + return stream_get_line($this->getOutputStream(), 4096, $end); } /** @@ -202,6 +220,14 @@ class RawConnection { * @psalm-assert null $this->process */ public function close(bool $terminate = true): void { + $this->close_process($terminate); + } + + /** + * @param bool $terminate + * @psalm-assert null $this->process + */ + protected function close_process(bool $terminate = true): void { if (!is_resource($this->process)) { return; } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Share.php b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Share.php index 68446d380e0..eb68d3800b3 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Share.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Share.php @@ -345,11 +345,17 @@ class Share extends AbstractShare { // since returned stream is closed by the caller we need to create a new instance // since we can't re-use the same file descriptor over multiple calls $connection = $this->getConnection(); + stream_set_blocking($connection->getOutputStream(), false); $connection->write('get ' . $source . ' ' . $this->system->getFD(5)); $connection->write('exit'); $fh = $connection->getFileOutputStream(); - stream_context_set_option($fh, 'file', 'connection', $connection); + $fh = CallbackWrapper::wrap($fh, function() use ($connection) { + $connection->write(''); + }); + if (!is_resource($fh)) { + throw new Exception("Failed to wrap file output"); + } return $fh; } @@ -374,7 +380,9 @@ class Share extends AbstractShare { // use a close callback to ensure the upload is finished before continuing // this also serves as a way to keep the connection in scope - $stream = CallbackWrapper::wrap($fh, null, null, function () use ($connection) { + $stream = CallbackWrapper::wrap($fh, function() use ($connection) { + $connection->write(''); + }, null, function () use ($connection) { $connection->close(false); // dont terminate, give the upload some time }); if (is_resource($stream)) { @@ -446,7 +454,7 @@ class Share extends AbstractShare { * @return string[] */ protected function execute(string $command): array { - $this->connect()->write($command . PHP_EOL); + $this->connect()->write($command); return $this->connect()->read(); } diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php index 7f6d8863350..6f8018746b3 100644 --- a/apps/files_external/lib/AppInfo/Application.php +++ b/apps/files_external/lib/AppInfo/Application.php @@ -31,8 +31,6 @@ namespace OCA\Files_External\AppInfo; use OCA\Files_External\Config\ConfigAdapter; use OCA\Files_External\Config\UserPlaceholderHandler; -use OCA\Files_External\Listener\GroupDeletedListener; -use OCA\Files_External\Listener\UserDeletedListener; use OCA\Files_External\Lib\Auth\AmazonS3\AccessKey; use OCA\Files_External\Lib\Auth\Builtin; use OCA\Files_External\Lib\Auth\NullMechanism; @@ -49,6 +47,7 @@ use OCA\Files_External\Lib\Auth\Password\UserGlobalAuth; use OCA\Files_External\Lib\Auth\Password\UserProvided; use OCA\Files_External\Lib\Auth\PublicKey\RSA; 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\Backend\AmazonS3; use OCA\Files_External\Lib\Backend\DAV; @@ -62,6 +61,8 @@ use OCA\Files_External\Lib\Backend\SMB_OC; 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\Listener\GroupDeletedListener; +use OCA\Files_External\Listener\UserDeletedListener; use OCA\Files_External\Service\BackendService; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -126,16 +127,16 @@ class Application extends App implements IBackendProvider, IAuthMechanismProvide $container = $this->getContainer(); $backends = [ - $container->query(Local::class), - $container->query(FTP::class), - $container->query(DAV::class), - $container->query(OwnCloud::class), - $container->query(SFTP::class), - $container->query(AmazonS3::class), - $container->query(Swift::class), - $container->query(SFTP_Key::class), - $container->query(SMB::class), - $container->query(SMB_OC::class), + $container->get(Local::class), + $container->get(FTP::class), + $container->get(DAV::class), + $container->get(OwnCloud::class), + $container->get(SFTP::class), + $container->get(AmazonS3::class), + $container->get(Swift::class), + $container->get(SFTP_Key::class), + $container->get(SMB::class), + $container->get(SMB_OC::class), ]; return $backends; @@ -149,37 +150,38 @@ class Application extends App implements IBackendProvider, IAuthMechanismProvide return [ // AuthMechanism::SCHEME_NULL mechanism - $container->query(NullMechanism::class), + $container->get(NullMechanism::class), // AuthMechanism::SCHEME_BUILTIN mechanism - $container->query(Builtin::class), + $container->get(Builtin::class), // AuthMechanism::SCHEME_PASSWORD mechanisms - $container->query(Password::class), - $container->query(SessionCredentials::class), - $container->query(LoginCredentials::class), - $container->query(UserProvided::class), - $container->query(GlobalAuth::class), - $container->query(UserGlobalAuth::class), + $container->get(Password::class), + $container->get(SessionCredentials::class), + $container->get(LoginCredentials::class), + $container->get(UserProvided::class), + $container->get(GlobalAuth::class), + $container->get(UserGlobalAuth::class), // AuthMechanism::SCHEME_OAUTH1 mechanisms - $container->query(OAuth1::class), + $container->get(OAuth1::class), // AuthMechanism::SCHEME_OAUTH2 mechanisms - $container->query(OAuth2::class), + $container->get(OAuth2::class), // AuthMechanism::SCHEME_PUBLICKEY mechanisms - $container->query(RSA::class), - $container->query(RSAPrivateKey::class), + $container->get(RSA::class), + $container->get(RSAPrivateKey::class), // AuthMechanism::SCHEME_OPENSTACK mechanisms - $container->query(OpenStackV2::class), - $container->query(OpenStackV3::class), - $container->query(Rackspace::class), + $container->get(OpenStackV2::class), + $container->get(OpenStackV3::class), + $container->get(Rackspace::class), // Specialized mechanisms - $container->query(AccessKey::class), - $container->query(KerberosAuth::class), + $container->get(AccessKey::class), + $container->get(KerberosAuth::class), + $container->get(KerberosApacheAuth::class), ]; } } diff --git a/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php b/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php new file mode 100644 index 00000000000..88aaa417a87 --- /dev/null +++ b/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php @@ -0,0 +1,53 @@ +<?php + +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @author Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_External\Lib\Auth\SMB; + +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\DefinitionParameter; +use OCP\Authentication\LoginCredentials\IStore; +use OCP\IL10N; + +class KerberosApacheAuth extends AuthMechanism { + /** @var IStore */ + private $credentialsStore; + + public function __construct(IL10N $l, IStore $credentialsStore) { + $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::kerberosapache') + ->setScheme(self::SCHEME_SMB) + ->setText($l->t('Kerberos ticket apache mode')) + ->addParameter($realm); + $this->credentialsStore = $credentialsStore; + } + + public function getCredentialsStore(): IStore { + return $this->credentialsStore; + } +} diff --git a/apps/files_external/lib/Lib/Backend/SMB.php b/apps/files_external/lib/Lib/Backend/SMB.php index 867648824ac..57ee866f3c7 100644 --- a/apps/files_external/lib/Lib/Backend/SMB.php +++ b/apps/files_external/lib/Lib/Backend/SMB.php @@ -24,16 +24,19 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ + namespace OCA\Files_External\Lib\Backend; use Icewind\SMB\BasicAuth; +use Icewind\SMB\KerberosApacheAuth; use Icewind\SMB\KerberosAuth; 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\DefinitionParameter; +use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; use OCA\Files_External\Lib\LegacyDependencyCheckPolyfill; use OCA\Files_External\Lib\StorageConfig; - use OCP\IL10N; use OCP\IUser; @@ -69,10 +72,6 @@ class SMB extends Backend { ->setLegacyAuthMechanism($legacyAuth); } - /** - * @param StorageConfig $storage - * @param IUser $user - */ public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { $auth = $storage->getAuthMechanism(); if ($auth->getScheme() === AuthMechanism::SCHEME_PASSWORD) { @@ -90,6 +89,45 @@ class SMB extends Backend { case 'smb::kerberos': $smbAuth = new KerberosAuth(); break; + case 'smb::kerberosapache': + if (!$auth instanceof KerberosApacheAuthMechanism) { + throw new \InvalidArgumentException('invalid authentication backend'); + } + $credentialsStore = $auth->getCredentialsStore(); + $kerbAuth = new KerberosApacheAuth(); + // check if a kerberos ticket is available, else fallback to session credentials + if ($kerbAuth->checkTicket()) { + $smbAuth = $kerbAuth; + } else { + try { + $credentials = $credentialsStore->getLoginCredentials(); + $user = $credentials->getLoginName(); + $pass = $credentials->getPassword(); + preg_match('/(.*)@(.*)/', $user, $matches); + $realm = $storage->getBackendOption('default_realm'); + if (empty($realm)) { + $realm = 'WORKGROUP'; + } + $userPart = $matches[1]; + $domainPart = $matches[2]; + if (count($matches) === 0) { + $username = $user; + $workgroup = $realm; + } else { + $username = $userPart; + $workgroup = $domainPart; + } + $smbAuth = new BasicAuth( + $username, + $workgroup, + $pass + ); + } catch (\Exception $e) { + throw new InsufficientDataForMeaningfulAnswerException('No session credentials saved'); + } + } + + break; default: throw new \InvalidArgumentException('unknown authentication backend'); } diff --git a/apps/files_external/tests/setup-krb.sh b/apps/files_external/tests/setup-krb.sh new file mode 100755 index 00000000000..968ba80529e --- /dev/null +++ b/apps/files_external/tests/setup-krb.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +function getContainerHealth { + docker inspect --format "{{.State.Health.Status}}" $1 +} + +function waitContainer { + while STATUS=$(getContainerHealth $1); [ $STATUS != "healthy" ]; do + if [ $STATUS == "unhealthy" ]; then + echo "Failed!" + exit -1 + fi + printf . + lf=$'\n' + sleep 1 + done + printf "$lf" +} + +mkdir /tmp/shared + +# start the dc +docker run -dit --name dc -v /tmp/shared:/shared --hostname krb.domain.test --cap-add SYS_ADMIN icewind1991/samba-krb-test-dc +DC_IP=$(docker inspect dc --format '{{.NetworkSettings.IPAddress}}') + +waitContainer dc + +# start apache +docker run -d --name apache -v $PWD:/var/www/html -v /tmp/shared:/shared --dns $DC_IP --hostname httpd.domain.test icewind1991/samba-krb-test-apache +APACHE_IP=$(docker inspect apache --format '{{.NetworkSettings.IPAddress}}') + +# 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 |