feat: Implement OAuth Backend App Flow for Email Accounts (#27167)
* feat: Implement OAuth Backend App Flow for Email Accounts * chore: Reformat to satisfy linter * chore: format Signed-off-by: Akhil Narang <me@akhilnarang.dev> --------- Signed-off-by: Akhil Narang <me@akhilnarang.dev> Co-authored-by: Akhil Narang <me@akhilnarang.dev>
This commit is contained in:
parent
82fd0f012a
commit
dde466be3d
4 changed files with 53 additions and 9 deletions
|
|
@ -199,7 +199,11 @@ frappe.ui.form.on("Email Account", {
|
|||
},
|
||||
|
||||
show_oauth_authorization_message(frm) {
|
||||
if (frm.doc.auth_method === "OAuth" && frm.doc.connected_app) {
|
||||
if (
|
||||
frm.doc.auth_method === "OAuth" &&
|
||||
frm.doc.connected_app &&
|
||||
!frm.doc.backend_app_flow
|
||||
) {
|
||||
frappe.call({
|
||||
method: "frappe.integrations.doctype.connected_app.connected_app.has_token",
|
||||
args: {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"frappe_mail_site",
|
||||
"authentication_column",
|
||||
"auth_method",
|
||||
"backend_app_flow",
|
||||
"authorize_api_access",
|
||||
"validate_frappe_mail_settings",
|
||||
"password",
|
||||
|
|
@ -99,7 +100,7 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.service != \"Frappe Mail\"",
|
||||
"depends_on": "eval: doc.service != \"Frappe Mail\" && !doc.backend_app_flow",
|
||||
"fieldname": "login_id_is_different",
|
||||
"fieldtype": "Check",
|
||||
"hide_days": 1,
|
||||
|
|
@ -581,7 +582,7 @@
|
|||
"label": "IMAP Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.auth_method === \"OAuth\" && doc.connected_app && doc.connected_user",
|
||||
"depends_on": "eval: doc.auth_method === \"OAuth\" && doc.connected_app && doc.connected_user && !doc.backend_app_flow",
|
||||
"fieldname": "authorize_api_access",
|
||||
"fieldtype": "Button",
|
||||
"label": "Authorize API Access"
|
||||
|
|
@ -610,11 +611,11 @@
|
|||
"options": "Connected App"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.auth_method === \"OAuth\"",
|
||||
"depends_on": "eval: doc.auth_method === \"OAuth\" && !doc.backend_app_flow",
|
||||
"fieldname": "connected_user",
|
||||
"fieldtype": "Link",
|
||||
"label": "Connected User",
|
||||
"mandatory_depends_on": "eval: doc.auth_method === \"OAuth\"",
|
||||
"mandatory_depends_on": "eval: doc.auth_method === \"OAuth\" && !doc.backend_app_flow",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
|
|
@ -684,12 +685,19 @@
|
|||
"fieldtype": "Password",
|
||||
"label": "API Secret",
|
||||
"mandatory_depends_on": "eval: doc.service == \"Frappe Mail\" && doc.auth_method == \"Basic\""
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.auth_method === \"OAuth\"",
|
||||
"fieldname": "backend_app_flow",
|
||||
"fieldtype": "Check",
|
||||
"label": "Authenticate as Service Principal"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-inbox",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-06-28 08:45:43.565934",
|
||||
"modified": "2024-07-18 11:05:57.193762",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Account",
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ class EmailAccount(Document):
|
|||
auth_method: DF.Literal["Basic", "OAuth"]
|
||||
auto_reply_message: DF.TextEditor | None
|
||||
awaiting_password: DF.Check
|
||||
backend_app_flow: DF.Check
|
||||
brand_logo: DF.AttachImage | None
|
||||
connected_app: DF.Link | None
|
||||
connected_user: DF.Link | None
|
||||
|
|
@ -780,7 +781,12 @@ class EmailAccount(Document):
|
|||
def get_oauth_token(self):
|
||||
if self.auth_method == "OAuth":
|
||||
connected_app = frappe.get_doc("Connected App", self.connected_app)
|
||||
return connected_app.get_active_token(self.connected_user)
|
||||
if self.backend_app_flow:
|
||||
token = connected_app.get_backend_app_token()
|
||||
else:
|
||||
token = connected_app.get_active_token(self.connected_user)
|
||||
|
||||
return token
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
@ -879,8 +885,10 @@ def pull(now=False):
|
|||
)
|
||||
|
||||
for email_account in email_accounts:
|
||||
if email_account.auth_method == "OAuth" and not has_token(
|
||||
email_account.connected_app, email_account.connected_user
|
||||
if (
|
||||
email_account.auth_method == "OAuth"
|
||||
and not email_account.backend_app_flow
|
||||
and not has_token(email_account.connected_app, email_account.connected_user)
|
||||
):
|
||||
# don't try to pull from accounts which dont have access token (for Oauth)
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import os
|
||||
from urllib.parse import urlencode, urljoin
|
||||
|
||||
from oauthlib.oauth2 import BackendApplicationClient
|
||||
from requests_oauthlib import OAuth2Session
|
||||
|
||||
import frappe
|
||||
|
|
@ -147,6 +148,29 @@ class ConnectedApp(Document):
|
|||
|
||||
return token_cache
|
||||
|
||||
def get_backend_app_token(self):
|
||||
"""Get an Access Token for the Cloud-Registered Service Principal"""
|
||||
# There is no User assigned to the app, so we give it an empty string,
|
||||
# otherwise it will assign the logged in user.
|
||||
token_cache = self.get_token_cache("")
|
||||
if token_cache is None:
|
||||
token_cache = frappe.new_doc("Token Cache")
|
||||
token_cache.connected_app = self.name
|
||||
elif not token_cache.is_expired():
|
||||
return token_cache
|
||||
|
||||
# Get a new Access token for the App
|
||||
client = BackendApplicationClient(client_id=self.client_id, scope=self.get_scopes())
|
||||
oauth_session = OAuth2Session(client=client)
|
||||
|
||||
token = oauth_session.fetch_token(self.token_uri, client_secret=self.get_password("client_secret"))
|
||||
|
||||
token_cache.update_data(token)
|
||||
token_cache.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
return token_cache
|
||||
|
||||
|
||||
@frappe.whitelist(methods=["GET"], allow_guest=True)
|
||||
def callback(code=None, state=None):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue