From 9cef15313e47b4ce34bfac6d022e5ffa7d435fe5 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 4 Oct 2024 00:48:04 -0700
Subject: [PATCH] Starting to mess with it, this basically works for Google
---
.../manage/mail/mail.component.html | 1 +
.../components/manage/mail/mail.component.ts | 32 ++++++++-
src-ui/src/app/data/ui-settings.ts | 6 ++
src/documents/views.py | 69 +++++++++++++++++++
src/paperless/settings.py | 8 +++
src/paperless/urls.py | 6 ++
6 files changed, 121 insertions(+), 1 deletion(-)
diff --git a/src-ui/src/app/components/manage/mail/mail.component.html b/src-ui/src/app/components/manage/mail/mail.component.html
index 296d80055..a83b87355 100644
--- a/src-ui/src/app/components/manage/mail/mail.component.html
+++ b/src-ui/src/app/components/manage/mail/mail.component.html
@@ -13,6 +13,7 @@
+ Connect with Google
-
diff --git a/src-ui/src/app/components/manage/mail/mail.component.ts b/src-ui/src/app/components/manage/mail/mail.component.ts
index 288e8e121..94a29a557 100644
--- a/src-ui/src/app/components/manage/mail/mail.component.ts
+++ b/src-ui/src/app/components/manage/mail/mail.component.ts
@@ -18,6 +18,9 @@ import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-ac
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
+import { SettingsService } from 'src/app/services/settings.service'
+import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
+import { ActivatedRoute } from '@angular/router'
@Component({
selector: 'pngx-mail',
@@ -32,13 +35,20 @@ export class MailComponent
mailRules: MailRule[] = []
unsubscribeNotifier: Subject = new Subject()
+ oAuthAccoundId: number
+
+ public get googleOAuthUrl(): string {
+ return this.settingsService.get(SETTINGS_KEYS.GOOGLE_OAUTH_URL)
+ }
constructor(
public mailAccountService: MailAccountService,
public mailRuleService: MailRuleService,
private toastService: ToastService,
private modalService: NgbModal,
- public permissionsService: PermissionsService
+ public permissionsService: PermissionsService,
+ private settingsService: SettingsService,
+ private route: ActivatedRoute
) {
super()
}
@@ -50,6 +60,13 @@ export class MailComponent
.subscribe({
next: (r) => {
this.mailAccounts = r.results
+ if (this.oAuthAccoundId) {
+ this.editMailAccount(
+ this.mailAccounts.find(
+ (account) => account.id === this.oAuthAccoundId
+ )
+ )
+ }
},
error: (e) => {
this.toastService.showError(
@@ -70,6 +87,19 @@ export class MailComponent
this.toastService.showError($localize`Error retrieving mail rules`, e)
},
})
+
+ this.route.queryParamMap.subscribe((params) => {
+ if (params.get('oauth_success')) {
+ this.oAuthAccoundId = parseInt(params.get('account_id'))
+ if (this.mailAccounts.length > 0) {
+ this.editMailAccount(
+ this.mailAccounts.find(
+ (account) => account.id === this.oAuthAccoundId
+ )
+ )
+ }
+ }
+ })
}
ngOnDestroy() {
diff --git a/src-ui/src/app/data/ui-settings.ts b/src-ui/src/app/data/ui-settings.ts
index ad88b2e57..3532c1b2f 100644
--- a/src-ui/src/app/data/ui-settings.ts
+++ b/src-ui/src/app/data/ui-settings.ts
@@ -64,6 +64,7 @@ export const SETTINGS_KEYS = {
SEARCH_DB_ONLY: 'general-settings:search:db-only',
SEARCH_FULL_TYPE: 'general-settings:search:more-link',
EMPTY_TRASH_DELAY: 'trash_delay',
+ GOOGLE_OAUTH_URL: 'google_oauth_url',
}
export const SETTINGS: UiSetting[] = [
@@ -242,4 +243,9 @@ export const SETTINGS: UiSetting[] = [
type: 'number',
default: 30,
},
+ {
+ key: SETTINGS_KEYS.GOOGLE_OAUTH_URL,
+ type: 'string',
+ default: '',
+ },
]
diff --git a/src/documents/views.py b/src/documents/views.py
index 94674a83f..a049d3bfd 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -14,6 +14,7 @@ from unicodedata import normalize
from urllib.parse import quote
from urllib.parse import urlparse
+import httpx
import pathvalidate
from django.apps import apps
from django.conf import settings
@@ -1554,6 +1555,16 @@ class UiSettingsView(GenericAPIView):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
serializer_class = UiSettingsViewSerializer
+ def generate_google_oauth_url(self) -> str:
+ token_request_uri = "https://accounts.google.com/o/oauth2/auth"
+ response_type = "code"
+ client_id = settings.GOOGLE_OAUTH_CLIENT_ID
+ redirect_uri = "http://localhost:8000/api/oauth/google/callback/"
+ scope = "https://mail.google.com/"
+ access_type = "offline"
+ url = f"{token_request_uri}?response_type={response_type}&client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&access_type={access_type}"
+ return url
+
def get(self, request, format=None):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
@@ -1584,6 +1595,9 @@ class UiSettingsView(GenericAPIView):
ui_settings["auditlog_enabled"] = settings.AUDIT_LOG_ENABLED
+ if settings.GOOGLE_OAUTH_ENABLED:
+ ui_settings["google_oauth_url"] = self.generate_google_oauth_url()
+
user_resp = {
"id": user.id,
"username": user.username,
@@ -2129,3 +2143,58 @@ class TrashView(ListModelMixin, PassUserMixin):
doc_ids = [doc.id for doc in docs]
empty_trash(doc_ids=doc_ids)
return Response({"result": "OK", "doc_ids": doc_ids})
+
+
+# Outlook https://stackoverflow.com/questions/73902642/office-365-imap-authentication-via-oauth2-and-python-msal-library
+class GoogleOauthCallbackView(GenericAPIView):
+ # permission_classes = (AllowAny,)
+
+ def get(self, request, format=None):
+ # Guide: https://postmansmtp.com/how-to-configure-post-smtp-with-gmailgsuite-using-oauth/
+ # http://localhost:4200/api/oauth/google/callback?code=4%2F0AQlEd8yxIwqjz95p82tWMq4ogn4KxRdprtjjGqjEHW4x7X1roEgswzn9EfiAit1cOLfSog&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&authuser=0&hd=michaelshamoon.com&prompt=consent
+ code = request.query_params.get("code")
+ if code is None:
+ return HttpResponseBadRequest("Code required")
+
+ token_request_uri = "https://accounts.google.com/o/oauth2/token"
+ client_id = settings.GOOGLE_OAUTH_CLIENT_ID
+ client_secret = settings.GOOGLE_OAUTH_CLIENT_SECRET
+ redirect_uri = "http://localhost:8000/api/oauth/google/callback/"
+ grant_type = "authorization_code"
+ scope = "https://mail.google.com/"
+ url = f"{token_request_uri}"
+ data = {
+ "code": code,
+ "client_id": client_id,
+ "client_secret": client_secret,
+ "redirect_uri": redirect_uri,
+ "grant_type": grant_type,
+ "scope": scope,
+ }
+ headers = {
+ "Content-Type": "application/x-www-form-urlencoded",
+ }
+ response = httpx.post(url, data=data, headers=headers)
+ data = response.json()
+ if "error" in data:
+ return HttpResponseBadRequest(data["error"])
+ elif "access_token" in data:
+ access_token = data["access_token"]
+ # if "refresh_token" in data:
+ # refresh_token = data["refresh_token"]
+ # expires_in = data["expires_in"]
+ account, _ = MailAccount.objects.update_or_create(
+ password=access_token,
+ is_token=True,
+ imap_server="imap.gmail.com",
+ defaults={
+ "name": f"Gmail {datetime.now()}",
+ "username": "",
+ "imap_security": MailAccount.ImapSecurity.SSL,
+ "imap_port": 993,
+ },
+ )
+
+ return HttpResponseRedirect(
+ f"http://localhost:4200/mail?oauth_success=1&account_id={account.pk}",
+ )
diff --git a/src/paperless/settings.py b/src/paperless/settings.py
index ab943f30f..e5dd7ce8e 100644
--- a/src/paperless/settings.py
+++ b/src/paperless/settings.py
@@ -1195,3 +1195,11 @@ EMAIL_ENABLE_GPG_DECRYPTOR: Final[bool] = __get_boolean(
# Soft Delete #
###############################################################################
EMPTY_TRASH_DELAY = max(__get_int("PAPERLESS_EMPTY_TRASH_DELAY", 30), 1)
+
+
+###############################################################################
+# Oauth Email Providers #
+###############################################################################
+GOOGLE_OAUTH_CLIENT_ID = os.getenv("PAPERLESS_GOOGLE_OAUTH_CLIENT_ID")
+GOOGLE_OAUTH_CLIENT_SECRET = os.getenv("PAPERLESS_GOOGLE_OAUTH_CLIENT_SECRET")
+GOOGLE_OAUTH_ENABLED = bool(GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET)
diff --git a/src/paperless/urls.py b/src/paperless/urls.py
index 4de9f3662..13b5c0136 100644
--- a/src/paperless/urls.py
+++ b/src/paperless/urls.py
@@ -22,6 +22,7 @@ from documents.views import CorrespondentViewSet
from documents.views import CustomFieldViewSet
from documents.views import DocumentTypeViewSet
from documents.views import GlobalSearchView
+from documents.views import GoogleOauthCallbackView
from documents.views import IndexView
from documents.views import LogViewSet
from documents.views import PostDocumentView
@@ -165,6 +166,11 @@ urlpatterns = [
TrashView.as_view(),
name="trash",
),
+ re_path(
+ r"^oauth/google/callback/",
+ GoogleOauthCallbackView.as_view(),
+ name="google_oauth_callback",
+ ),
*api_router.urls,
],
),