From f95aad871341172c02e5abc701bd68e12bfaf60c Mon Sep 17 00:00:00 2001 From: ralf Date: Fri, 7 Jul 2023 10:49:58 +0200 Subject: [PATCH] * Mail: REST API to send mails including attachments / sharing links, or open interactive compose windows WIP Mail REST API: enable direct sending of mails with token authentication / no password not allowing to save mail to Sent folder --- api/src/CalDAV.php | 5 +++-- api/src/Mail/Account.php | 7 ++++--- api/src/Mail/Credentials.php | 5 +++++ mail/inc/class.mail_compose.inc.php | 11 ++++++++--- mail/src/ApiHandler.php | 23 ++++++++++++++++++----- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/api/src/CalDAV.php b/api/src/CalDAV.php index 353edb9f51..23bd177ec3 100644 --- a/api/src/CalDAV.php +++ b/api/src/CalDAV.php @@ -1501,7 +1501,8 @@ class CalDAV extends HTTP_WebDAV_Server // for some reason OS X Addressbook (CFNetwork user-agent) uses now (DAV:add-member given with collection URL+"?add-member") // POST to the collection URL plus a UID like name component (like for regular PUT) to create new entrys if (isset($_GET['add-member']) || Handler::get_agent() == 'cfnetwork' || - substr($options['path'], -1) === '/' && self::isJSON()) + // addressbook has not implemented a POST handler, therefore we have to call the PUT handler + preg_match('#^(/[^/]+)?/addressbook(-[^/]+)?/$', $options['path']) && self::isJSON()) { $_GET['add-member'] = ''; // otherwise we give no Location header return $this->PUT($options, 'POST'); @@ -2027,7 +2028,7 @@ class CalDAV extends HTTP_WebDAV_Server // check/handle Prefer: return-representation if ($status[0] === '2' || $status === true) { - // we can NOT use 204 No content (forbidds a body) with return=representation, therefore we need to use 200 Ok instead! + // we can NOT use 204 No content (forbids a body) with return=representation, therefore we need to use 200 Ok instead! if ($handler->check_return_representation($options, $id, $user) && (int)$status == 204) { $status = '200 Ok'; diff --git a/api/src/Mail/Account.php b/api/src/Mail/Account.php index 87c48d91c6..f1fb4392af 100644 --- a/api/src/Mail/Account.php +++ b/api/src/Mail/Account.php @@ -273,7 +273,7 @@ class Account implements \ArrayAccess $GLOBALS['egw_info']['user']['account_id'] && (!isset($called_for) || $called_for == $GLOBALS['egw_info']['user']['account_id'])) { - // get usename/password from current user, let it overwrite credentials for all/no session + // get username/password from current user, let it overwrite credentials for all/no session $params = Credentials::from_session( (!isset($called_for) ? array() : array('acc_smtp_auth_session' => false)) + $params, !isset($called_for) ) + $params; @@ -1594,9 +1594,10 @@ class Account implements \ArrayAccess * @param boolean $smtp =false false: usable for IMAP, true: usable for SMTP * @param boolean $return_id =false true: return acc_id, false return account object * @param boolean $log_no_default =true true: error_log if no default found, false be silent + * @param boolean $user_context =true false: we have no user context, need a smtp-only account or one without password * @return Account|int|null */ - static function get_default($smtp=false, $return_id=false, $log_no_default=true) + static function get_default($smtp=false, $return_id=false, $log_no_default=true, $user_context=true) { try { @@ -1606,7 +1607,7 @@ class Account implements \ArrayAccess { if (!$params['acc_smtp_host'] || !$params['acc_smtp_port']) continue; // check requirement of session, which is not available in async service! - if (isset($GLOBALS['egw_info']['flags']['async-service']) || + if (!$user_context || isset($GLOBALS['egw_info']['flags']['async-service']) || empty($GLOBALS['egw_info']['user']['account_id'])) // happens during login when notifying about blocked accounts { if ($params['acc_smtp_auth_session']) continue; diff --git a/api/src/Mail/Credentials.php b/api/src/Mail/Credentials.php index 7771128a3a..bf8fc85c3b 100644 --- a/api/src/Mail/Credentials.php +++ b/api/src/Mail/Credentials.php @@ -324,6 +324,11 @@ class Credentials throw new Api\Exception\WrongParameter("Unknown data[acc_imap_logintype]=".array2string($data['acc_imap_logintype']).'!'); } $password = base64_decode(Api\Cache::getSession('phpgwapi', 'password')); + // if session password is a token, do NOT use it, but also do NOT throw, just return NULL for the password(s) + if (Api\Auth\Token::isToken($password)) + { + $password = null; + } $realname = !$set_identity || !empty($data['ident_realname']) ? $data['ident_realname'] : ($GLOBALS['egw_info']['user']['account_fullname'] ?? null); $email = !$set_identity || !empty($data['ident_email']) ? $data['ident_email'] : diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index 4b4faec804..1dc3cc324f 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -2936,10 +2936,10 @@ class mail_compose return $messageUid; } - function send($_formData) + function send($_formData, int $_acc_id=null) { $mail_bo = $this->mail_bo; - $mail = new Api\Mailer($mail_bo->profileID); + $mail = new Api\Mailer($_acc_id ?: $mail_bo->profileID); $messageIsDraft = false; $this->sessionData['mailaccount'] = $_formData['mailaccount']; @@ -3073,7 +3073,12 @@ class mail_compose // we use the sentFolder settings of the choosen mailaccount // sentFolder is account specific $changeProfileOnSentFolderNeeded = false; - if ($_formData['serverID']!=$_formData['mailaccount']) + if ($this->mailPreferences['sendOptions'] === 'send_only') + { + // no need to check for Sent folder + $sentFolder = 'none'; + } + elseif ($_formData['serverID']!=$_formData['mailaccount']) { $this->changeProfile($_formData['mailaccount']); //error_log(__METHOD__.__LINE__.'#'.$this->mail_bo->profileID.'<->'.$mail_bo->profileID.'#'); diff --git a/mail/src/ApiHandler.php b/mail/src/ApiHandler.php index e0c4a8fa84..2b0fa04cb4 100644 --- a/mail/src/ApiHandler.php +++ b/mail/src/ApiHandler.php @@ -94,22 +94,35 @@ class ApiHandler extends Api\CalDAV\Handler ], self::JSON_RESPONSE_OPTIONS); return true; } - - $compose = new \mail_compose($acc_id=Api\Mail\Account::read_identity($ident_id)['acc_id']); + $acc_id = Api\Mail\Account::read_identity($ident_id)['acc_id']; + $mail_account = Api\Mail\Account::read($acc_id); + // check if the mail-account requires a user-context / password and then just send the mail with an smtp-only account NOT saving to Sent folder + if (empty($mail_account->acc_imap_password) || $mail_account->acc_smtp_auth_session && empty($mail_account->acc_smtp_password)) + { + $acc_id = Api\Mail\Account::get_default(true, true, true, false); + $compose = new \mail_compose($acc_id); + $compose->mailPreferences['sendOptions'] = 'send_only'; + $warning = 'Mail NOT saved to Sent folder, as no user password'; + } + else + { + $compose = new \mail_compose($acc_id); + } $preset = array_filter([ 'mailaccount' => $acc_id, 'mailidentity' => $ident_id, 'identity' => null, 'add_signature' => true, // add signature in send, independent what preference says ]+$preset); - if ($compose->send($preset)) + if ($compose->send($preset, $acc_id)) { header('Content-Type: application/json'); - echo json_encode([ + echo json_encode(array_filter([ 'status' => 200, + 'warning' => $warning ?? null, 'message' => 'Mail successful sent', //'data' => $preset, - ], self::JSON_RESPONSE_OPTIONS); + ]), self::JSON_RESPONSE_OPTIONS); return true; } throw new \Exception($compose->error_info);