From 2bfb63b382bef725c2cc338f470a92937b162afe Mon Sep 17 00:00:00 2001 From: jquiros2 <33005938+jquiros2@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:14:43 -0600 Subject: [PATCH 1/4] modoboa2.php adding modoboa api v2 support. might figure out how to get helpful info back to the user: I needed to get these from modoboa error logs to help a user: `400 Bad Request` response: {"new_password":["Password must contain at least 1 uppercase letter."]} `400 Bad Request` response: {"new_password":["The password is too similar to the username."]} --- plugins/password/drivers/modoboa2.php | 117 ++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 plugins/password/drivers/modoboa2.php diff --git a/plugins/password/drivers/modoboa2.php b/plugins/password/drivers/modoboa2.php new file mode 100644 index 0000000000..459397c79f --- /dev/null +++ b/plugins/password/drivers/modoboa2.php @@ -0,0 +1,117 @@ +config->get('password_modoboa_api_token'); + $IMAPhost = $_SESSION['imap_host']; + // uncomment and add your url if you use a differente url for your modoboa instance and api: + //$IMAPhost = 'mymodoboa.somewhere.net'; + + // Use v2 API: search user by username + $client = password::get_http_client(); + $url = "https://{$IMAPhost}/api/v2/accounts/?search=" . urlencode($username); + + $options = [ + 'http_errors' => true, + 'headers' => [ + 'Authorization' => "Token " . $token, + 'Accept' => 'application/json', + ], + ]; + + // Call GET to fetch user details + try { + $response = $client->get($url, $options); + $responseBody = $response->getBody()->getContents(); + } catch (\Exception $e) { + rcube::raise_error("Password plugin: Error fetching {$url} : {$e->getMessage()}", true); + return PASSWORD_CONNECT_ERROR; + } + + // Decode json string + $decoded = json_decode($responseBody); + + if (!is_array($decoded) || empty($decoded)) { + return PASSWORD_CONNECT_ERROR; + } + + // Get user ID (pk) + $userid = $decoded[0]->pk; + + // Call v2 password endpoint with form-encoded data + $url2 = "https://{$IMAPhost}/api/v2/accounts/" . $userid . "/password/"; + $options2 = [ + 'http_errors' => true, + 'headers' => [ + 'Authorization' => "Token " . $token, + 'Accept' => 'application/json', + // Content-Type will be set automatically when using form_params + ], + 'form_params' => [ + 'password' => $curpass, // current password + 'new_password' => $passwd // new password + ], + ]; + + // Execute password change + try { + $response2 = $client->put($url2, $options2); + $responseBody2 = $response2->getBody()->getContents(); + $httpCode = $response2->getStatusCode(); + } catch (\Exception $e) { + rcube::raise_error("Password plugin: Error on {$url2} : {$e->getMessage()}", true); + return PASSWORD_CONNECT_ERROR; + } + + // Check for success + if ($httpCode >= 200 && $httpCode < 300) { + return PASSWORD_SUCCESS; + } + + // Log for debugging if needed + rcube::raise_error("Password plugin: Unexpected response from {$url2}. HTTP {$httpCode}. Response: {$responseBody2}", true); + return PASSWORD_CONNECT_ERROR; + } +} +?> From bed7f35ed8f2f5fe95203575cf1501358d779136 Mon Sep 17 00:00:00 2001 From: jquiros2 <33005938+jquiros2@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:26:30 -0600 Subject: [PATCH 2/4] modoboa api v2 compat with modoboa api v2. still need better error feedback to user. ["The password is too similar to the username."]} {"new_password":["Password must contain at least 1 uppercase letter."]} {"new_password":["Password must contain at least 1 digit."]} etc currently need to be seend in the modoboa error.log to give user feedback when failed attempt. --- plugins/password/drivers/modoboa.php | 75 +++++++++++++++++----------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/plugins/password/drivers/modoboa.php b/plugins/password/drivers/modoboa.php index 5532a15b1a..459397c79f 100644 --- a/plugins/password/drivers/modoboa.php +++ b/plugins/password/drivers/modoboa.php @@ -2,11 +2,14 @@ /** * Modoboa Password Driver + * Minor modification from 2.0, updated to use Modoboa v2 password endpoint. + * Payload uses form-encoded data: current password and new password. + * Endpoint: PUT /api/v2/accounts/{id}/password/ * - * Payload is json string containing username, oldPassword and newPassword - * Return value is a json string saying result: true if success. + * Return value is a status constant (PASSWORD_SUCCESS on success, + * PASSWORD_CONNECT_ERROR on failure). * - * @version 2.0 + * @version 2.1 * * @author stephane @actionweb.fr * @@ -25,9 +28,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see https://www.gnu.org/licenses/. * - * The driver need modoboa core 1.10.6 or later + * The driver needs Modoboa core 1.10.6 or later * - * You need to define theses variables in plugin/password/config.inc.php + * You need to define these variables in plugin/password/config.inc.php * * $config['password_driver'] = 'modoboa'; // use modoboa as driver * $config['password_modoboa_api_token'] = ''; // put token number from Modoboa server @@ -40,59 +43,75 @@ public function save($curpass, $passwd, $username) { // Init config access $rcmail = rcmail::get_instance(); - $token = $rcmail->config->get('password_modoboa_api_token'); + $token = $rcmail->config->get('password_modoboa_api_token'); $IMAPhost = $_SESSION['imap_host']; + // uncomment and add your url if you use a differente url for your modoboa instance and api: + //$IMAPhost = 'mymodoboa.somewhere.net'; + // Use v2 API: search user by username $client = password::get_http_client(); - $url = "https://{$IMAPhost}/api/v1/accounts/?search=" . urlencode($username); + $url = "https://{$IMAPhost}/api/v2/accounts/?search=" . urlencode($username); $options = [ 'http_errors' => true, 'headers' => [ - 'Authorization' => "Token {$token}", - 'Cache-Control' => 'no-cache', - 'Content-Type' => 'application/json', + 'Authorization' => "Token " . $token, + 'Accept' => 'application/json', ], ]; - // Call GET to fetch values from modoboa server + // Call GET to fetch user details try { $response = $client->get($url, $options); - $response = $response->getBody()->getContents(); + $responseBody = $response->getBody()->getContents(); } catch (\Exception $e) { rcube::raise_error("Password plugin: Error fetching {$url} : {$e->getMessage()}", true); return PASSWORD_CONNECT_ERROR; } // Decode json string - $decoded = json_decode($response); + $decoded = json_decode($responseBody); - if (!is_array($decoded)) { + if (!is_array($decoded) || empty($decoded)) { return PASSWORD_CONNECT_ERROR; } // Get user ID (pk) $userid = $decoded[0]->pk; - // Encode json with new password - $options['body'] = json_encode([ - 'username' => $decoded[0]->username, - 'mailbox' => $decoded[0]->mailbox, - 'role' => $decoded[0]->role, - 'password' => $passwd, // new password - ]); - - $url = "https://{$IMAPhost}/api/v1/accounts/{$userid}/"; + // Call v2 password endpoint with form-encoded data + $url2 = "https://{$IMAPhost}/api/v2/accounts/" . $userid . "/password/"; + $options2 = [ + 'http_errors' => true, + 'headers' => [ + 'Authorization' => "Token " . $token, + 'Accept' => 'application/json', + // Content-Type will be set automatically when using form_params + ], + 'form_params' => [ + 'password' => $curpass, // current password + 'new_password' => $passwd // new password + ], + ]; - // Call HTTP API Modoboa + // Execute password change try { - $response = $client->put($url, $options); - $response = $response->getBody()->getContents(); + $response2 = $client->put($url2, $options2); + $responseBody2 = $response2->getBody()->getContents(); + $httpCode = $response2->getStatusCode(); } catch (\Exception $e) { - rcube::raise_error("Password plugin: Error on {$url} : {$e->getMessage()}", true); + rcube::raise_error("Password plugin: Error on {$url2} : {$e->getMessage()}", true); return PASSWORD_CONNECT_ERROR; } - return PASSWORD_SUCCESS; + // Check for success + if ($httpCode >= 200 && $httpCode < 300) { + return PASSWORD_SUCCESS; + } + + // Log for debugging if needed + rcube::raise_error("Password plugin: Unexpected response from {$url2}. HTTP {$httpCode}. Response: {$responseBody2}", true); + return PASSWORD_CONNECT_ERROR; } } +?> From b3003cb97184e50f7a20fb87beeaf41394343a5f Mon Sep 17 00:00:00 2001 From: jquiros1 Date: Fri, 26 Sep 2025 11:33:25 -0600 Subject: [PATCH 3/4] tried adding modoboa2.php as a driver and changing driver in config, but probably need to register it somewhere so just leaving with original modoboa.php name. --- plugins/password/drivers/modoboa2.php | 117 -------------------------- 1 file changed, 117 deletions(-) delete mode 100644 plugins/password/drivers/modoboa2.php diff --git a/plugins/password/drivers/modoboa2.php b/plugins/password/drivers/modoboa2.php deleted file mode 100644 index 459397c79f..0000000000 --- a/plugins/password/drivers/modoboa2.php +++ /dev/null @@ -1,117 +0,0 @@ -config->get('password_modoboa_api_token'); - $IMAPhost = $_SESSION['imap_host']; - // uncomment and add your url if you use a differente url for your modoboa instance and api: - //$IMAPhost = 'mymodoboa.somewhere.net'; - - // Use v2 API: search user by username - $client = password::get_http_client(); - $url = "https://{$IMAPhost}/api/v2/accounts/?search=" . urlencode($username); - - $options = [ - 'http_errors' => true, - 'headers' => [ - 'Authorization' => "Token " . $token, - 'Accept' => 'application/json', - ], - ]; - - // Call GET to fetch user details - try { - $response = $client->get($url, $options); - $responseBody = $response->getBody()->getContents(); - } catch (\Exception $e) { - rcube::raise_error("Password plugin: Error fetching {$url} : {$e->getMessage()}", true); - return PASSWORD_CONNECT_ERROR; - } - - // Decode json string - $decoded = json_decode($responseBody); - - if (!is_array($decoded) || empty($decoded)) { - return PASSWORD_CONNECT_ERROR; - } - - // Get user ID (pk) - $userid = $decoded[0]->pk; - - // Call v2 password endpoint with form-encoded data - $url2 = "https://{$IMAPhost}/api/v2/accounts/" . $userid . "/password/"; - $options2 = [ - 'http_errors' => true, - 'headers' => [ - 'Authorization' => "Token " . $token, - 'Accept' => 'application/json', - // Content-Type will be set automatically when using form_params - ], - 'form_params' => [ - 'password' => $curpass, // current password - 'new_password' => $passwd // new password - ], - ]; - - // Execute password change - try { - $response2 = $client->put($url2, $options2); - $responseBody2 = $response2->getBody()->getContents(); - $httpCode = $response2->getStatusCode(); - } catch (\Exception $e) { - rcube::raise_error("Password plugin: Error on {$url2} : {$e->getMessage()}", true); - return PASSWORD_CONNECT_ERROR; - } - - // Check for success - if ($httpCode >= 200 && $httpCode < 300) { - return PASSWORD_SUCCESS; - } - - // Log for debugging if needed - rcube::raise_error("Password plugin: Unexpected response from {$url2}. HTTP {$httpCode}. Response: {$responseBody2}", true); - return PASSWORD_CONNECT_ERROR; - } -} -?> From 03e00dfdba8ae4afd9577df377c1615d000eb5df Mon Sep 17 00:00:00 2001 From: jquiros2 <33005938+jquiros2@users.noreply.github.com> Date: Sun, 28 Sep 2025 07:50:47 -0600 Subject: [PATCH 4/4] updated modoboa version comment requires modoboa v2 api. https://github.com/modoboa/modoboa/releases/tag/2.0.0 --- plugins/password/drivers/modoboa.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/password/drivers/modoboa.php b/plugins/password/drivers/modoboa.php index 459397c79f..c848e111bc 100644 --- a/plugins/password/drivers/modoboa.php +++ b/plugins/password/drivers/modoboa.php @@ -28,7 +28,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see https://www.gnu.org/licenses/. * - * The driver needs Modoboa core 1.10.6 or later + * The driver needs Modoboa core 2.0.0 or later * * You need to define these variables in plugin/password/config.inc.php *