Starting to mess with it, this basically works for Google

This commit is contained in:
shamoon 2024-10-04 00:48:04 -07:00
parent 95d1abd416
commit 9cef15313e
6 changed files with 121 additions and 1 deletions

View File

@ -13,6 +13,7 @@
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editMailAccount()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailAccount }"> <button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editMailAccount()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailAccount }">
<i-bs name="plus-circle"></i-bs>&nbsp;<ng-container i18n>Add Account</ng-container> <i-bs name="plus-circle"></i-bs>&nbsp;<ng-container i18n>Add Account</ng-container>
</button> </button>
<a class="btn btn-sm btn-outline-primary ms-2" [href]="googleOAuthUrl" target="_blank" i18n>Connect with Google</a>
</h4> </h4>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">

View File

@ -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 { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component' import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.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({ @Component({
selector: 'pngx-mail', selector: 'pngx-mail',
@ -32,13 +35,20 @@ export class MailComponent
mailRules: MailRule[] = [] mailRules: MailRule[] = []
unsubscribeNotifier: Subject<any> = new Subject() unsubscribeNotifier: Subject<any> = new Subject()
oAuthAccoundId: number
public get googleOAuthUrl(): string {
return this.settingsService.get(SETTINGS_KEYS.GOOGLE_OAUTH_URL)
}
constructor( constructor(
public mailAccountService: MailAccountService, public mailAccountService: MailAccountService,
public mailRuleService: MailRuleService, public mailRuleService: MailRuleService,
private toastService: ToastService, private toastService: ToastService,
private modalService: NgbModal, private modalService: NgbModal,
public permissionsService: PermissionsService public permissionsService: PermissionsService,
private settingsService: SettingsService,
private route: ActivatedRoute
) { ) {
super() super()
} }
@ -50,6 +60,13 @@ export class MailComponent
.subscribe({ .subscribe({
next: (r) => { next: (r) => {
this.mailAccounts = r.results this.mailAccounts = r.results
if (this.oAuthAccoundId) {
this.editMailAccount(
this.mailAccounts.find(
(account) => account.id === this.oAuthAccoundId
)
)
}
}, },
error: (e) => { error: (e) => {
this.toastService.showError( this.toastService.showError(
@ -70,6 +87,19 @@ export class MailComponent
this.toastService.showError($localize`Error retrieving mail rules`, e) 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() { ngOnDestroy() {

View File

@ -64,6 +64,7 @@ export const SETTINGS_KEYS = {
SEARCH_DB_ONLY: 'general-settings:search:db-only', SEARCH_DB_ONLY: 'general-settings:search:db-only',
SEARCH_FULL_TYPE: 'general-settings:search:more-link', SEARCH_FULL_TYPE: 'general-settings:search:more-link',
EMPTY_TRASH_DELAY: 'trash_delay', EMPTY_TRASH_DELAY: 'trash_delay',
GOOGLE_OAUTH_URL: 'google_oauth_url',
} }
export const SETTINGS: UiSetting[] = [ export const SETTINGS: UiSetting[] = [
@ -242,4 +243,9 @@ export const SETTINGS: UiSetting[] = [
type: 'number', type: 'number',
default: 30, default: 30,
}, },
{
key: SETTINGS_KEYS.GOOGLE_OAUTH_URL,
type: 'string',
default: '',
},
] ]

View File

@ -14,6 +14,7 @@ from unicodedata import normalize
from urllib.parse import quote from urllib.parse import quote
from urllib.parse import urlparse from urllib.parse import urlparse
import httpx
import pathvalidate import pathvalidate
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
@ -1554,6 +1555,16 @@ class UiSettingsView(GenericAPIView):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions) permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
serializer_class = UiSettingsViewSerializer 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): def get(self, request, format=None):
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
@ -1584,6 +1595,9 @@ class UiSettingsView(GenericAPIView):
ui_settings["auditlog_enabled"] = settings.AUDIT_LOG_ENABLED 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 = { user_resp = {
"id": user.id, "id": user.id,
"username": user.username, "username": user.username,
@ -2129,3 +2143,58 @@ class TrashView(ListModelMixin, PassUserMixin):
doc_ids = [doc.id for doc in docs] doc_ids = [doc.id for doc in docs]
empty_trash(doc_ids=doc_ids) empty_trash(doc_ids=doc_ids)
return Response({"result": "OK", "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}",
)

View File

@ -1195,3 +1195,11 @@ EMAIL_ENABLE_GPG_DECRYPTOR: Final[bool] = __get_boolean(
# Soft Delete # # Soft Delete #
############################################################################### ###############################################################################
EMPTY_TRASH_DELAY = max(__get_int("PAPERLESS_EMPTY_TRASH_DELAY", 30), 1) 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)

View File

@ -22,6 +22,7 @@ from documents.views import CorrespondentViewSet
from documents.views import CustomFieldViewSet from documents.views import CustomFieldViewSet
from documents.views import DocumentTypeViewSet from documents.views import DocumentTypeViewSet
from documents.views import GlobalSearchView from documents.views import GlobalSearchView
from documents.views import GoogleOauthCallbackView
from documents.views import IndexView from documents.views import IndexView
from documents.views import LogViewSet from documents.views import LogViewSet
from documents.views import PostDocumentView from documents.views import PostDocumentView
@ -165,6 +166,11 @@ urlpatterns = [
TrashView.as_view(), TrashView.as_view(),
name="trash", name="trash",
), ),
re_path(
r"^oauth/google/callback/",
GoogleOauthCallbackView.as_view(),
name="google_oauth_callback",
),
*api_router.urls, *api_router.urls,
], ],
), ),