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 }">
<i-bs name="plus-circle"></i-bs>&nbsp;<ng-container i18n>Add Account</ng-container>
</button>
<a class="btn btn-sm btn-outline-primary ms-2" [href]="googleOAuthUrl" target="_blank" i18n>Connect with Google</a>
</h4>
<ul class="list-group">
<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 { 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<any> = 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() {

View File

@ -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: '',
},
]

View File

@ -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}",
)

View File

@ -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)

View File

@ -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,
],
),