Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,56 @@ Add the following to your `config.php`:
[BasicAuth_0]: https://en.wikipedia.org/wiki/Basic_access_authentication


HTTP (Generic HTTP Auth Interface)
------

Authenticate users against a generic HTTP authentication interface. This backend sends HTTP POST requests with user credentials to a configured endpoint and expects specific response codes for authentication validation.

### Configuration
The HTTP backend accepts three parameters: endpoint URL, optional hash algorithm, and optional access key.

**⚠⚠ Warning:** make sure to use the URL of a correctly configured HTTP authentication server. The server must validate credentials properly and return HTTP 202 for successful authentication. ⚠⚠

Add the following to your `config.php`:

'user_backends' => array(
array(
'class' => '\OCA\UserExternal\HTTP',
'arguments' => array('https://example.com/auth_endpoint'),
),
),

For password hashing (e.g., MD5, SHA1, SHA256), add the hash algorithm as second parameter:

'user_backends' => array(
array(
'class' => '\OCA\UserExternal\HTTP',
'arguments' => array('https://example.com/auth_endpoint', 'md5'),
),
Comment on lines +192 to +193
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation recommends using weak hash algorithms for passwords (e.g., md5, sha1) in arguments for the HTTP backend. This encourages insecure configurations and could lead to credential compromise. Fix by removing weak algorithms from examples and explicitly recommending no client-side hashing (rely on HTTPS), or only strong algorithms (e.g., sha256) with proper server-side handling.

Copilot uses AI. Check for mistakes.
),

For additional security with an access key:

'user_backends' => array(
array(
'class' => '\OCA\UserExternal\HTTP',
'arguments' => array('https://example.com/auth_endpoint', 'sha1', 'your_secret_access_key'),
),
),

### HTTP Request Format
The backend sends POST requests with the following parameters:
- `user`: The username
- `password`: The password (hashed if hash algorithm is specified)
- `accessKey`: The access key (if provided)

### Expected Response
The HTTP server must return HTTP status code **202** for successful authentication. Any other status code is treated as authentication failure.

### Dependencies
Uses Nextcloud's built-in HTTP client service (no additional dependencies required).


SSH
---

Expand Down
3 changes: 2 additions & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>user_external</id>
<name>External user authentication</name>
<summary>Use external user authentication methods like IMAP, SMB, FTP, WebDAV, HTTP BasicAuth, SSH and XMPP</summary>
<summary>Use external user authentication methods like IMAP, SMB, FTP, WebDAV, HTTP BasicAuth, HTTP (Generic), SSH and XMPP</summary>
<description><![CDATA[Use external user authentication methods to give users acces to your Nextcloud instance:

* IMAP
* SMB
* FTP
* WebDAV
* HTTP BasicAuth
* HTTP (Generic HTTP Auth Interface)
* SSH
* XMPP

Expand Down
115 changes: 115 additions & 0 deletions lib/HTTP.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php
/**
* Copyright (c) 2021 Sebastian Sterk <sebastian@wiuwiu.de>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/

namespace OCA\UserExternal;

use OCP\Http\Client\IClientService;

/**
* User authentication against a generic HTTP auth interface
*
* @category Apps
* @package UserExternal
* @author Sebastian Sterk https://wiuwiu.de/Imprint
* @license http://www.gnu.org/licenses/agpl AGPL
*/
class HTTP extends Base {
private $hashAlgo;
private $accessKey;
private $authenticationEndpoint;
private $httpClientService;

/**
* Create new HTTP authentication provider
*
* @param string $authenticationEndpoint The HTTP endpoint URL for authentication
* @param string|false $hashAlgo Hash algorithm for password (false for plain text)
* @param string $accessKey Access key for additional security
*/
public function __construct($authenticationEndpoint, $hashAlgo = false, $accessKey = '') {
parent::__construct($authenticationEndpoint);
$this->authenticationEndpoint = $authenticationEndpoint;
$this->hashAlgo = $hashAlgo;
$this->accessKey = $accessKey;
$this->httpClientService = \OC::$server->get(IClientService::class);
}

/**
* Send user credentials to HTTP endpoint
*
* @param string $user The username
* @param string $password The password
*
* @return bool True if authentication successful, false otherwise
*/
public function sendUserData($user, $password) {
if ($this->hashAlgo !== false) {
$password = $this->hashPassword($password);
}

try {
$client = $this->httpClientService->newClient();

Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove trailing whitespace on this line.

Suggested change

Copilot uses AI. Check for mistakes.
$response = $client->post($this->authenticationEndpoint, [
'form_params' => [
'accessKey' => $this->accessKey,
'user' => $user,
'password' => $password
],
Comment on lines +58 to +63
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The accessKey parameter is always included in the POST request even when it's empty (default value is ''). Consider only including it in the form_params when it's non-empty to avoid sending unnecessary data. This would make the integration cleaner for endpoints that don't require an access key.

Suggested change
$response = $client->post($this->authenticationEndpoint, [
'form_params' => [
'accessKey' => $this->accessKey,
'user' => $user,
'password' => $password
],
$formParams = [
'user' => $user,
'password' => $password
];
if ($this->accessKey !== '') {
$formParams['accessKey'] = $this->accessKey;
}
$response = $client->post($this->authenticationEndpoint, [
'form_params' => $formParams,

Copilot uses AI. Check for mistakes.
'timeout' => 10,
]);

$statusCode = $response->getStatusCode();

Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove trailing whitespace on this line.

Suggested change

Copilot uses AI. Check for mistakes.
if ($statusCode === 202) {
return true;
} else {
return false;
}
Comment on lines +69 to +73
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This if-else block can be simplified to return $statusCode === 202; for better readability and conciseness.

Suggested change
if ($statusCode === 202) {
return true;
} else {
return false;
}
return $statusCode === 202;

Copilot uses AI. Check for mistakes.
} catch (\Exception $e) {
\OC::$server->getLogger()->error(
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use $this->logger instead of \OC::$server->getLogger(). The Base class provides a logger property that should be used for consistency with other authentication backends in this codebase (e.g., FTP, BasicAuth, IMAP).

Suggested change
\OC::$server->getLogger()->error(
$this->logger->error(

Copilot uses AI. Check for mistakes.
'ERROR: Could not connect to HTTP auth endpoint: ' . $e->getMessage(),
['app' => 'user_external']
);
return false;
}
}

/**
* Hash password using configured algorithm
*
* @param string $password The plain text password
*
* @return string The hashed password
*/
private function hashPassword($password) {
return hash($this->hashAlgo, $password);
Comment on lines +90 to +91
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hash() function should be validated to ensure $this->hashAlgo is a valid algorithm. Consider adding validation in the constructor (e.g., in_array($hashAlgo, hash_algos())) to fail early with a clear error message if an invalid algorithm is provided.

Copilot uses AI. Check for mistakes.
}
Comment on lines +91 to +92
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passwords are hashed client-side using hash($this->hashAlgo, $password) with user-provided algorithms (e.g., md5, sha1). Weak algorithms like MD5/SHA1 are unsuitable for password protection and enable credential disclosure if intercepted or reused, and client-side hashing lacks proper salting/work factors. Fix by removing client-side password hashing and rely on TLS to protect credentials in transit, or strictly enforce a strong algorithm (e.g., sha256) with HMAC and server-side verification; do not allow md5/sha1 for passwords.

Copilot uses AI. Check for mistakes.

/**
* Check if the password is correct without logging in the user
*
* @param string $uid The username
* @param string $password The password
*
* @return string|false The username on success, false on failure
*/
public function checkPassword($uid, $password) {
if (isset($uid) && isset($password)) {
$authenticationStatus = $this->sendUserData($uid, $password);
if ($authenticationStatus) {
$uid = mb_strtolower($uid);
$this->storeUser($uid);
return $uid;
} else {
return false;
}
}
return false;
}
}
8 changes: 8 additions & 0 deletions tests/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,12 @@
'user' => 'test',//valid username/password combination
'password' => 'test',
),
'http' => array(
'run' => false,
'endpoint' => 'http://localhost/http_auth',
'hashAlgo' => false, // or 'md5', 'sha1', etc.
'accessKey' => 'your_access_key',
'user' => 'test',//valid username/password combination
'password' => 'test',
),
);
40 changes: 40 additions & 0 deletions tests/http.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/**
* Copyright (c) 2021 Sebastian Sterk <sebastian@wiuwiu.de>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/

class Test_User_HTTP extends \Test\TestCase {
/**
* @var OC_User_HTTP $instance
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PHPDoc type should be \OCA\UserExternal\HTTP instead of OC_User_HTTP. The OC_User_* naming appears to be used only in tests for backward compatibility, but the actual class uses the namespaced format.

Suggested change
* @var OC_User_HTTP $instance
* @var \OCA\UserExternal\HTTP $instance

Copilot uses AI. Check for mistakes.
*/
private $instance;

private function getConfig() {
return include(__DIR__.'/config.php');
}

public function skip() {
$config = $this->getConfig();
$this->skipUnless($config['http']['run']);
}

protected function setUp() {
parent::setUp();

$config = $this->getConfig();
$this->instance = new OC_User_HTTP(
$config['http']['endpoint'],
$config['http']['hashAlgo'],
$config['http']['accessKey']
);
}

public function testLogin() {
$config = $this->getConfig();
$this->assertEquals($config['http']['user'], $this->instance->checkPassword($config['http']['user'], $config['http']['password']));
$this->assertFalse($this->instance->checkPassword($config['http']['user'], $config['http']['password'].'foo'));
}
}
Loading