Compare commits
11 Commits
514a7c5ed3
...
b86ecbfc99
| Author | SHA1 | Date | |
|---|---|---|---|
| b86ecbfc99 | |||
|
|
51feb76b47 | ||
|
|
21b7ad5b3a | ||
|
|
65b7b2b5e6 | ||
|
|
21fa01c4ba | ||
|
|
79ae3e9235 | ||
|
|
6d4405ecff | ||
|
|
2884c6e749 | ||
|
|
47e8099502 | ||
|
|
412e397069 | ||
|
|
8b18c65014 |
51
.github/workflows/command-rebase.yml
vendored
51
.github/workflows/command-rebase.yml
vendored
@@ -1,51 +0,0 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Rebase command
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: created
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
rebase:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: none
|
||||
|
||||
# On pull requests and if the comment starts with `/rebase`
|
||||
if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/rebase')
|
||||
|
||||
steps:
|
||||
- name: Add reaction on start
|
||||
uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reaction-type: "+1"
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691 # 1.8
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
|
||||
- name: Add reaction on failure
|
||||
uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
|
||||
if: failure()
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reaction-type: "-1"
|
||||
@@ -220,9 +220,11 @@ Add the following to your `config.php`:
|
||||
**⚠⚠ Warning:** If you need to set *5 (Hashed Password in Database)* to false, your Prosody Instance is storing passwords in plaintext. This is insecure and not recommended. We highly recommend that you change your Prosody configuration to protect the passwords of your Prosody users. ⚠⚠
|
||||
|
||||
|
||||
## SMTP
|
||||
Works the same way as the IMAP section above, but authenticates against an SMTP server.
|
||||
|
||||
Alternatives
|
||||
------------
|
||||
Other extensions allow connecting to external user databases directly via SQL, which may be faster:
|
||||
|
||||
* [user_sql](https://github.com/nextcloud/user_sql)
|
||||
* [user_backend_sql_raw](https://github.com/PanCakeConnaisseur/user_backend_sql_raw)
|
||||
|
||||
@@ -11,12 +11,13 @@
|
||||
* FTP
|
||||
* WebDAV
|
||||
* HTTP BasicAuth
|
||||
* SMTP
|
||||
* SSH
|
||||
* XMPP
|
||||
|
||||
Read the [documentation](https://github.com/nextcloud/user_external#readme) to learn how to configure it!
|
||||
]]></description>
|
||||
<version>3.4.0</version>
|
||||
<version>3.5.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Robin Appelman</author>
|
||||
<namespace>UserExternal</namespace>
|
||||
@@ -33,6 +34,6 @@ Read the [documentation](https://github.com/nextcloud/user_external#readme) to l
|
||||
<bugs>https://github.com/nextcloud/user_external/issues</bugs>
|
||||
<repository type="git">https://github.com/nextcloud/user_external.git</repository>
|
||||
<dependencies>
|
||||
<nextcloud min-version="25" max-version="29" />
|
||||
<nextcloud min-version="25" max-version="32" />
|
||||
</dependencies>
|
||||
</info>
|
||||
|
||||
31
composer.lock
generated
31
composer.lock
generated
@@ -3707,26 +3707,23 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.25.0",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c"
|
||||
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c",
|
||||
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.23-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
@@ -3770,7 +3767,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3786,7 +3783,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-04T08:16:47+00:00"
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
@@ -3869,16 +3866,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v5.4.7",
|
||||
"version": "v5.4.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "38a44b2517b470a436e1c944bf9b9ba3961137fb"
|
||||
"reference": "01906871cb9b5e3cf872863b91aba4ec9767daf4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/38a44b2517b470a436e1c944bf9b9ba3961137fb",
|
||||
"reference": "38a44b2517b470a436e1c944bf9b9ba3961137fb",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/01906871cb9b5e3cf872863b91aba4ec9767daf4",
|
||||
"reference": "01906871cb9b5e3cf872863b91aba4ec9767daf4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3911,7 +3908,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v5.4.7"
|
||||
"source": "https://github.com/symfony/process/tree/v5.4.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3927,7 +3924,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-18T16:18:52+00:00"
|
||||
"time": "2024-11-06T09:18:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
@@ -4279,5 +4276,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "7.3"
|
||||
},
|
||||
"plugin-api-version": "2.3.0"
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
*/
|
||||
namespace OCA\UserExternal;
|
||||
|
||||
use OCP\Server;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Base class for external auth implementations that stores users
|
||||
* on their first login in a local table.
|
||||
@@ -23,6 +26,7 @@ namespace OCA\UserExternal;
|
||||
*/
|
||||
abstract class Base extends \OC\User\Backend {
|
||||
protected $backend = '';
|
||||
protected readonly LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* Create new instance, set backend name
|
||||
@@ -31,6 +35,7 @@ abstract class Base extends \OC\User\Backend {
|
||||
*/
|
||||
public function __construct($backend) {
|
||||
$this->backend = $backend;
|
||||
$this->logger = Server::get(LoggerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,14 +37,14 @@ class BasicAuth extends Base {
|
||||
);
|
||||
$canary = get_headers($this->authUrl, 1, $context);
|
||||
if (!$canary) {
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: Not possible to connect to BasicAuth Url: '.$this->authUrl,
|
||||
['app' => 'user_external']
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!isset(array_change_key_case($canary, CASE_LOWER)['www-authenticate'])) {
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: Mis-configured BasicAuth Url: '.$this->authUrl.', provided URL does not do authentication!',
|
||||
['app' => 'user_external']
|
||||
);
|
||||
@@ -61,7 +61,7 @@ class BasicAuth extends Base {
|
||||
$headers = get_headers($this->authUrl, 1, $context);
|
||||
|
||||
if (!$headers) {
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: Not possible to connect to BasicAuth Url: '.$this->authUrl,
|
||||
['app' => 'user_external']
|
||||
);
|
||||
@@ -82,7 +82,7 @@ class BasicAuth extends Base {
|
||||
$this->storeUser($uid);
|
||||
return $uid;
|
||||
case "3":
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: Too many redirects from BasicAuth Url: '.$this->authUrl,
|
||||
['app' => 'user_external']
|
||||
);
|
||||
|
||||
@@ -48,7 +48,7 @@ class FTP extends Base {
|
||||
*/
|
||||
public function checkPassword($uid, $password) {
|
||||
if (false === array_search($this->protocol, stream_get_wrappers())) {
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: Stream wrapper not available: ' . $this->protocol,
|
||||
['app' => 'user_external']
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ class IMAP extends Base {
|
||||
$uid = $pieces[0];
|
||||
}
|
||||
} else {
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: User has a wrong domain! Expecting: '.$this->domain,
|
||||
['app' => 'user_external']
|
||||
);
|
||||
@@ -111,7 +111,7 @@ class IMAP extends Base {
|
||||
$errorcode === 28) {
|
||||
# This is not defined in PHP-8.x
|
||||
# 28: CURLE_OPERATION_TIMEDOUT
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: Could not connect to imap server via curl: ' . curl_strerror($errorcode),
|
||||
['app' => 'user_external']
|
||||
);
|
||||
@@ -122,12 +122,12 @@ class IMAP extends Base {
|
||||
# 9: CURLE_REMOTE_ACCESS_DENIED
|
||||
# 67: CURLE_LOGIN_DENIED
|
||||
# 94: CURLE_AUTH_ERROR)
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: IMAP Login failed via curl: ' . curl_strerror($errorcode),
|
||||
['app' => 'user_external']
|
||||
);
|
||||
} else {
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: IMAP server returned an error: ' . $errorcode . ' / ' . curl_strerror($errorcode),
|
||||
['app' => 'user_external']
|
||||
);
|
||||
|
||||
@@ -43,7 +43,7 @@ class SMB extends Base {
|
||||
$command = self::SMBCLIENT.' '.escapeshellarg('//' . $this->host . '/dummy').' -U '.$uidEscaped.'%'.$password;
|
||||
$lastline = exec($command, $output, $retval);
|
||||
if ($retval === 127) {
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: smbclient executable missing',
|
||||
['app' => 'user_external']
|
||||
);
|
||||
@@ -56,7 +56,7 @@ class SMB extends Base {
|
||||
goto login;
|
||||
} elseif ($retval !== 0) {
|
||||
//some other error
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: smbclient error: ' . trim($lastline),
|
||||
['app' => 'user_external']
|
||||
);
|
||||
|
||||
141
lib/SMTP.php
Normal file
141
lib/SMTP.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
* @author Jonas Sulzer <jonas@violoncello.ch>
|
||||
* @copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* @copyright (c) 2026 Yakumo Laboratories -- https://yakumolabs.privatedns.org --
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
namespace OCA\UserExternal;
|
||||
|
||||
/**
|
||||
* User authentication against an SMTP mail server
|
||||
* (modified from IMAP.php)
|
||||
*
|
||||
* @category Apps
|
||||
* @package UserExternal
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
* @license http://www.gnu.org/licenses/agpl AGPL
|
||||
* @link http://github.com/owncloud/apps
|
||||
*/
|
||||
class SMTP extends Base {
|
||||
private $mailbox;
|
||||
private $port;
|
||||
private $sslmode;
|
||||
private $domain;
|
||||
private $stripeDomain;
|
||||
private $groupDomain;
|
||||
|
||||
/**
|
||||
* Create new SMTP authentication provider
|
||||
*
|
||||
* @param string $mailbox SMTP server domain/IP
|
||||
* @param int $port SMTP server $port
|
||||
* @param string $sslmode
|
||||
* @param string $domain If provided, loging will be restricted to this domain
|
||||
* @param boolean $stripeDomain (whether to stripe the domain part from the username or not)
|
||||
* @param boolean $groupDomain (whether to add the usere to a group corresponding to the domain of the address)
|
||||
*/
|
||||
public function __construct($mailbox, $port = null, $sslmode = null, $domain = null, $stripeDomain = true, $groupDomain = false) {
|
||||
parent::__construct($mailbox);
|
||||
$this->mailbox = $mailbox;
|
||||
$this->port = $port === null ? 25 : $port;
|
||||
$this->sslmode = $sslmode;
|
||||
$this->domain = $domain === null ? '' : $domain;
|
||||
$this->stripeDomain = $stripeDomain;
|
||||
$this->groupDomain = $groupDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the password is correct without logging in the user
|
||||
*
|
||||
* @param string $uid The username
|
||||
* @param string $password The password
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
public function checkPassword($uid, $password) {
|
||||
// Replace escaped @ symbol in uid (which is a mail address)
|
||||
// but only if there is no @ symbol and if there is a %40 inside the uid
|
||||
if (!(strpos($uid, '@') !== false) && (strpos($uid, '%40') !== false)) {
|
||||
$uid = str_replace("%40", "@", $uid);
|
||||
}
|
||||
|
||||
$pieces = explode('@', $uid);
|
||||
if ($this->domain !== '') {
|
||||
if (count($pieces) === 1) {
|
||||
$username = $uid . '@' . $this->domain;
|
||||
} elseif (count($pieces) === 2 && $pieces[1] === $this->domain) {
|
||||
$username = $uid;
|
||||
if ($this->stripeDomain) {
|
||||
$uid = $pieces[0];
|
||||
}
|
||||
} else {
|
||||
$this->logger->error(
|
||||
'ERROR: User has a wrong domain! Expecting: '.$this->domain,
|
||||
['app' => 'user_external']
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$username = $uid;
|
||||
}
|
||||
|
||||
$groups = [];
|
||||
if ((count($pieces) > 1) && $this->groupDomain && $pieces[1]) {
|
||||
$groups[] = $pieces[1];
|
||||
}
|
||||
|
||||
$protocol = ($this->sslmode === "ssl") ? "smtps" : "smtp";
|
||||
$url = "{$protocol}://{$this->mailbox}:{$this->port}";
|
||||
$ch = curl_init();
|
||||
if ($this->sslmode === 'tls') {
|
||||
curl_setopt($ch, CURLOPT_USE_SSL, CURLUSESSL_ALL);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_USERPWD, $username.":".$password);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
||||
|
||||
curl_exec($ch);
|
||||
$errorcode = curl_errno($ch);
|
||||
|
||||
if ($errorcode === 0) {
|
||||
curl_close($ch);
|
||||
$uid = mb_strtolower($uid);
|
||||
$this->storeUser($uid, $groups);
|
||||
return $uid;
|
||||
} elseif ($errorcode === CURLE_COULDNT_CONNECT ||
|
||||
$errorcode === CURLE_SSL_CONNECT_ERROR ||
|
||||
$errorcode === 28) {
|
||||
# This is not defined in PHP-8.x
|
||||
# 28: CURLE_OPERATION_TIMEDOUT
|
||||
$this->logger->error(
|
||||
'ERROR: Could not connect to smtp server via curl: ' . curl_strerror($errorcode),
|
||||
['app' => 'user_external']
|
||||
);
|
||||
} elseif ($errorcode === 9 ||
|
||||
$errorcode === 67 ||
|
||||
$errorcode === 94) {
|
||||
# These are not defined in PHP-8.x
|
||||
# 9: CURLE_REMOTE_ACCESS_DENIED
|
||||
# 67: CURLE_LOGIN_DENIED
|
||||
# 94: CURLE_AUTH_ERROR)
|
||||
$this->logger->error(
|
||||
'ERROR: SMTP Login failed via curl: ' . curl_strerror($errorcode),
|
||||
['app' => 'user_external']
|
||||
);
|
||||
} else {
|
||||
$this->logger->error(
|
||||
'ERROR: SMTP server returned an error: ' . $errorcode . ' / ' . curl_strerror($errorcode),
|
||||
['app' => 'user_external']
|
||||
);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ class SSH extends Base {
|
||||
*/
|
||||
public function checkPassword($uid, $password) {
|
||||
if (!extension_loaded('ssh2')) {
|
||||
\OC::$server->getLogger()->error(
|
||||
$this->logger->error(
|
||||
'ERROR: php-ssh2 PECL module missing',
|
||||
['app' => 'user_external']
|
||||
);
|
||||
|
||||
@@ -27,14 +27,14 @@ class WebDavAuth extends Base {
|
||||
public function checkPassword($uid, $password) {
|
||||
$arr = explode('://', $this->webDavAuthUrl, 2);
|
||||
if (! isset($arr) or count($arr) !== 2) {
|
||||
\OC::$server->getLogger()->error('ERROR: Invalid WebdavUrl: "'.$this->webDavAuthUrl.'" ', ['app' => 'user_external']);
|
||||
$this->logger->error('ERROR: Invalid WebdavUrl: "'.$this->webDavAuthUrl.'" ', ['app' => 'user_external']);
|
||||
return false;
|
||||
}
|
||||
list($protocol, $path) = $arr;
|
||||
$url = $protocol.'://'.urlencode($uid).':'.urlencode($password).'@'.$path;
|
||||
$headers = get_headers($url);
|
||||
if ($headers === false) {
|
||||
\OC::$server->getLogger()->error('ERROR: Not possible to connect to WebDAV Url: "'.$protocol.'://'.$path.'" ', ['app' => 'user_external']);
|
||||
$this->logger->error('ERROR: Not possible to connect to WebDAV Url: "'.$protocol.'://'.$path.'" ', ['app' => 'user_external']);
|
||||
return false;
|
||||
}
|
||||
$returnCode = substr($headers[0], 9, 3);
|
||||
|
||||
Reference in New Issue
Block a user