diff --git a/README.md b/README.md index 271ab6f..deb14b2 100644 --- a/README.md +++ b/README.md @@ -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'), + ), + ), + +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 --- diff --git a/appinfo/info.xml b/appinfo/info.xml index 34cbe12..3e333a6 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -3,7 +3,7 @@ xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd"> user_external External user authentication - Use external user authentication methods like IMAP, SMB, FTP, WebDAV, HTTP BasicAuth, SSH and XMPP + Use external user authentication methods like IMAP, SMB, FTP, WebDAV, HTTP BasicAuth, HTTP (Generic), SSH and XMPP + * 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(); + + $response = $client->post($this->authenticationEndpoint, [ + 'form_params' => [ + 'accessKey' => $this->accessKey, + 'user' => $user, + 'password' => $password + ], + 'timeout' => 10, + ]); + + $statusCode = $response->getStatusCode(); + + if ($statusCode === 202) { + return true; + } else { + return false; + } + } catch (\Exception $e) { + \OC::$server->getLogger()->error( + '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); + } + + /** + * 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; + } +} diff --git a/tests/config.php b/tests/config.php index 3cfadba..8bd30e8 100644 --- a/tests/config.php +++ b/tests/config.php @@ -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', + ), ); diff --git a/tests/http.php b/tests/http.php new file mode 100644 index 0000000..885d6ef --- /dev/null +++ b/tests/http.php @@ -0,0 +1,40 @@ + + * 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 + */ + 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')); + } +}