Skip to content

Commit 6fca339

Browse files
feat: add http post generic auth
Signed-off-by: Sebastian Sterk <7263970+sebastiansterk@users.noreply.github.com>
1 parent 2aeb5a2 commit 6fca339

5 files changed

Lines changed: 215 additions & 1 deletion

File tree

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,56 @@ Add the following to your `config.php`:
165165
[BasicAuth_0]: https://en.wikipedia.org/wiki/Basic_access_authentication
166166

167167

168+
HTTP (Generic HTTP Auth Interface)
169+
------
170+
171+
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.
172+
173+
### Configuration
174+
The HTTP backend accepts three parameters: endpoint URL, optional hash algorithm, and optional access key.
175+
176+
**⚠⚠ 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. ⚠⚠
177+
178+
Add the following to your `config.php`:
179+
180+
'user_backends' => array(
181+
array(
182+
'class' => '\OCA\UserExternal\HTTP',
183+
'arguments' => array('https://example.com/auth_endpoint'),
184+
),
185+
),
186+
187+
For password hashing (e.g., MD5, SHA1, SHA256), add the hash algorithm as second parameter:
188+
189+
'user_backends' => array(
190+
array(
191+
'class' => '\OCA\UserExternal\HTTP',
192+
'arguments' => array('https://example.com/auth_endpoint', 'md5'),
193+
),
194+
),
195+
196+
For additional security with an access key:
197+
198+
'user_backends' => array(
199+
array(
200+
'class' => '\OCA\UserExternal\HTTP',
201+
'arguments' => array('https://example.com/auth_endpoint', 'sha1', 'your_secret_access_key'),
202+
),
203+
),
204+
205+
### HTTP Request Format
206+
The backend sends POST requests with the following parameters:
207+
- `user`: The username
208+
- `password`: The password (hashed if hash algorithm is specified)
209+
- `accessKey`: The access key (if provided)
210+
211+
### Expected Response
212+
The HTTP server must return HTTP status code **202** for successful authentication. Any other status code is treated as authentication failure.
213+
214+
### Dependencies
215+
Uses Nextcloud's built-in HTTP client service (no additional dependencies required).
216+
217+
168218
SSH
169219
---
170220

appinfo/info.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
44
<id>user_external</id>
55
<name>External user authentication</name>
6-
<summary>Use external user authentication methods like IMAP, SMB, FTP, WebDAV, HTTP BasicAuth, SSH and XMPP</summary>
6+
<summary>Use external user authentication methods like IMAP, SMB, FTP, WebDAV, HTTP BasicAuth, HTTP (Generic), SSH and XMPP</summary>
77
<description><![CDATA[Use external user authentication methods to give users acces to your Nextcloud instance:
88
99
* IMAP
1010
* SMB
1111
* FTP
1212
* WebDAV
1313
* HTTP BasicAuth
14+
* HTTP (Generic HTTP Auth Interface)
1415
* SSH
1516
* XMPP
1617

lib/HTTP.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
/**
3+
* Copyright (c) 2021 Sebastian Sterk <sebastian@wiuwiu.de>
4+
* This file is licensed under the Affero General Public License version 3 or
5+
* later.
6+
* See the COPYING-README file.
7+
*/
8+
9+
namespace OCA\UserExternal;
10+
11+
use OCP\Http\Client\IClientService;
12+
13+
/**
14+
* User authentication against a generic HTTP auth interface
15+
*
16+
* @category Apps
17+
* @package UserExternal
18+
* @author Sebastian Sterk https://wiuwiu.de/Imprint
19+
* @license http://www.gnu.org/licenses/agpl AGPL
20+
*/
21+
class HTTP extends Base {
22+
private $hashAlgo;
23+
private $accessKey;
24+
private $authenticationEndpoint;
25+
private $httpClientService;
26+
27+
/**
28+
* Create new HTTP authentication provider
29+
*
30+
* @param string $authenticationEndpoint The HTTP endpoint URL for authentication
31+
* @param string|false $hashAlgo Hash algorithm for password (false for plain text)
32+
* @param string $accessKey Access key for additional security
33+
*/
34+
public function __construct($authenticationEndpoint, $hashAlgo = false, $accessKey = '') {
35+
parent::__construct($authenticationEndpoint);
36+
$this->authenticationEndpoint = $authenticationEndpoint;
37+
$this->hashAlgo = $hashAlgo;
38+
$this->accessKey = $accessKey;
39+
$this->httpClientService = \OC::$server->get(IClientService::class);
40+
}
41+
42+
/**
43+
* Send user credentials to HTTP endpoint
44+
*
45+
* @param string $user The username
46+
* @param string $password The password
47+
*
48+
* @return bool True if authentication successful, false otherwise
49+
*/
50+
public function sendUserData($user, $password) {
51+
if ($this->hashAlgo !== false) {
52+
$password = $this->hashPassword($password);
53+
}
54+
55+
try {
56+
$client = $this->httpClientService->newClient();
57+
58+
$response = $client->post($this->authenticationEndpoint, [
59+
'form_params' => [
60+
'accessKey' => $this->accessKey,
61+
'user' => $user,
62+
'password' => $password
63+
],
64+
'timeout' => 10,
65+
]);
66+
67+
$statusCode = $response->getStatusCode();
68+
69+
if ($statusCode === 202) {
70+
return true;
71+
} else {
72+
return false;
73+
}
74+
} catch (\Exception $e) {
75+
\OC::$server->getLogger()->error(
76+
'ERROR: Could not connect to HTTP auth endpoint: ' . $e->getMessage(),
77+
['app' => 'user_external']
78+
);
79+
return false;
80+
}
81+
}
82+
83+
/**
84+
* Hash password using configured algorithm
85+
*
86+
* @param string $password The plain text password
87+
*
88+
* @return string The hashed password
89+
*/
90+
private function hashPassword($password) {
91+
return hash($this->hashAlgo, $password);
92+
}
93+
94+
/**
95+
* Check if the password is correct without logging in the user
96+
*
97+
* @param string $uid The username
98+
* @param string $password The password
99+
*
100+
* @return string|false The username on success, false on failure
101+
*/
102+
public function checkPassword($uid, $password) {
103+
if (isset($uid) && isset($password)) {
104+
$authenticationStatus = $this->sendUserData($uid, $password);
105+
if ($authenticationStatus) {
106+
$uid = mb_strtolower($uid);
107+
$this->storeUser($uid);
108+
return $uid;
109+
} else {
110+
return false;
111+
}
112+
}
113+
return false;
114+
}
115+
}

tests/config.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,12 @@
3232
'user' => 'test',//valid username/password combination
3333
'password' => 'test',
3434
),
35+
'http' => array(
36+
'run' => false,
37+
'endpoint' => 'localhost/http_auth',
38+
'hashAlgo' => false, // or 'md5', 'sha1', etc.
39+
'accessKey' => 'your_access_key',
40+
'user' => 'test',//valid username/password combination
41+
'password' => 'test',
42+
),
3543
);

tests/http.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/**
3+
* Copyright (c) 2021 Sebastian Sterk <sebastian@wiuwiu.de>
4+
* This file is licensed under the Affero General Public License version 3 or
5+
* later.
6+
* See the COPYING-README file.
7+
*/
8+
9+
class Test_User_HTTP extends \Test\TestCase {
10+
/**
11+
* @var OC_User_HTTP $instance
12+
*/
13+
private $instance;
14+
15+
private function getConfig() {
16+
return include(__DIR__.'/config.php');
17+
}
18+
19+
public function skip() {
20+
$config = $this->getConfig();
21+
$this->skipUnless($config['http']['run']);
22+
}
23+
24+
protected function setUp() {
25+
parent::setUp();
26+
27+
$config = $this->getConfig();
28+
$this->instance = new OC_User_HTTP(
29+
$config['http']['endpoint'],
30+
$config['http']['hashAlgo'],
31+
$config['http']['accessKey']
32+
);
33+
}
34+
35+
public function testLogin() {
36+
$config = $this->getConfig();
37+
$this->assertEquals($config['http']['user'], $this->instance->checkPassword($config['http']['user'], $config['http']['password']));
38+
$this->assertFalse($this->instance->checkPassword($config['http']['user'], $config['http']['password'].'foo'));
39+
}
40+
}

0 commit comments

Comments
 (0)