Basic refresh
This commit is contained in:
parent
a4215f76dd
commit
3386eaee6d
@ -8,6 +8,7 @@ import tempfile
|
|||||||
import urllib
|
import urllib
|
||||||
import zipfile
|
import zipfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import mktime
|
from time import mktime
|
||||||
from unicodedata import normalize
|
from unicodedata import normalize
|
||||||
@ -1562,7 +1563,7 @@ class UiSettingsView(GenericAPIView):
|
|||||||
redirect_uri = "http://localhost:8000/api/oauth/callback/"
|
redirect_uri = "http://localhost:8000/api/oauth/callback/"
|
||||||
scope = "https://mail.google.com/"
|
scope = "https://mail.google.com/"
|
||||||
access_type = "offline"
|
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}"
|
url = f"{token_request_uri}?response_type={response_type}&client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&access_type={access_type}&prompt=consent"
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def generate_outlook_oauth_url(self) -> str:
|
def generate_outlook_oauth_url(self) -> str:
|
||||||
@ -2220,7 +2221,6 @@ class OauthCallbackView(GenericAPIView):
|
|||||||
}
|
}
|
||||||
response = httpx.post(token_request_uri, data=data, headers=headers)
|
response = httpx.post(token_request_uri, data=data, headers=headers)
|
||||||
data = response.json()
|
data = response.json()
|
||||||
logger.debug(data)
|
|
||||||
|
|
||||||
if "error" in data:
|
if "error" in data:
|
||||||
logger.error(f"Error {response.status_code} getting access token: {data}")
|
logger.error(f"Error {response.status_code} getting access token: {data}")
|
||||||
@ -2229,13 +2229,14 @@ class OauthCallbackView(GenericAPIView):
|
|||||||
)
|
)
|
||||||
elif "access_token" in data:
|
elif "access_token" in data:
|
||||||
access_token = data["access_token"]
|
access_token = data["access_token"]
|
||||||
# if "refresh_token" in data:
|
refresh_token = data["refresh_token"]
|
||||||
# refresh_token = data["refresh_token"]
|
expires_in = data["expires_in"]
|
||||||
# expires_in = data["expires_in"]
|
|
||||||
account, _ = MailAccount.objects.update_or_create(
|
account, _ = MailAccount.objects.update_or_create(
|
||||||
password=access_token,
|
password=access_token,
|
||||||
is_token=True,
|
is_token=True,
|
||||||
imap_server=imap_server,
|
imap_server=imap_server,
|
||||||
|
refresh_token=refresh_token,
|
||||||
|
expiration=timezone.now() + timedelta(seconds=expires_in),
|
||||||
defaults=defaults,
|
defaults=defaults,
|
||||||
)
|
)
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
|
@ -11,6 +11,7 @@ from fnmatch import fnmatch
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import httpx
|
||||||
import magic
|
import magic
|
||||||
import pathvalidate
|
import pathvalidate
|
||||||
from celery import chord
|
from celery import chord
|
||||||
@ -18,6 +19,7 @@ from celery import shared_task
|
|||||||
from celery.canvas import Signature
|
from celery.canvas import Signature
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import DatabaseError
|
from django.db import DatabaseError
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.timezone import is_naive
|
from django.utils.timezone import is_naive
|
||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
from imap_tools import AND
|
from imap_tools import AND
|
||||||
@ -514,6 +516,46 @@ class MailAccountHandler(LoggingMixin):
|
|||||||
"Unknown correspondent selector",
|
"Unknown correspondent selector",
|
||||||
) # pragma: no cover
|
) # pragma: no cover
|
||||||
|
|
||||||
|
def refresh_token(self, account: MailAccount) -> bool:
|
||||||
|
"""
|
||||||
|
Refreshes the token for the given mail account.
|
||||||
|
"""
|
||||||
|
if not account.refresh_token:
|
||||||
|
self.log.error(f"Account {account}: No refresh token available.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if "gmail" in account.imap_server:
|
||||||
|
data = {
|
||||||
|
"client_id": settings.GMAIL_OAUTH_CLIENT_ID,
|
||||||
|
"client_secret": settings.GMAIL_OAUTH_CLIENT_SECRET,
|
||||||
|
"refresh_token": account.refresh_token,
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
}
|
||||||
|
elif "outlook" in account.imap_server:
|
||||||
|
data = {
|
||||||
|
"client_id": settings.OUTLOOK_OAUTH_CLIENT_ID,
|
||||||
|
"client_secret": settings.OUTLOOK_OAUTH_CLIENT_SECRET,
|
||||||
|
"refresh_token": account.refresh_token,
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = httpx.post(
|
||||||
|
"https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||||
|
data=data,
|
||||||
|
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||||
|
)
|
||||||
|
data = response.json()
|
||||||
|
if "access_token" in data:
|
||||||
|
account.token = data["access_token"]
|
||||||
|
account.expiration = datetime.datetime.now() + timedelta(
|
||||||
|
seconds=data["expires_in"],
|
||||||
|
)
|
||||||
|
account.save()
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.log.error(f"Failed to refresh token for account {account}: {data}")
|
||||||
|
return False
|
||||||
|
|
||||||
def handle_mail_account(self, account: MailAccount):
|
def handle_mail_account(self, account: MailAccount):
|
||||||
"""
|
"""
|
||||||
Main entry method to handle a specific mail account.
|
Main entry method to handle a specific mail account.
|
||||||
@ -530,6 +572,13 @@ class MailAccountHandler(LoggingMixin):
|
|||||||
account.imap_port,
|
account.imap_port,
|
||||||
account.imap_security,
|
account.imap_security,
|
||||||
) as M:
|
) as M:
|
||||||
|
if account.is_token and account.expiration < timezone.now():
|
||||||
|
self.log.debug(f"Attempting to refresh token for account {account}")
|
||||||
|
success = self.refresh_token(account)
|
||||||
|
if not success:
|
||||||
|
self.log.error(f"Failed to refresh token for account {account}")
|
||||||
|
return total_processed_files
|
||||||
|
|
||||||
supports_gmail_labels = "X-GM-EXT-1" in M.client.capabilities
|
supports_gmail_labels = "X-GM-EXT-1" in M.client.capabilities
|
||||||
supports_auth_plain = "AUTH=PLAIN" in M.client.capabilities
|
supports_auth_plain = "AUTH=PLAIN" in M.client.capabilities
|
||||||
|
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-05 07:42
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("paperless_mail", "0026_mailrule_enabled"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="mailaccount",
|
||||||
|
name="expiration",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The expiration date of the refresh token. ",
|
||||||
|
null=True,
|
||||||
|
verbose_name="expiration",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="mailaccount",
|
||||||
|
name="refresh_token",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The refresh token to use for token authentication e.g. with oauth2.",
|
||||||
|
max_length=2048,
|
||||||
|
null=True,
|
||||||
|
verbose_name="refresh token",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -51,6 +51,25 @@ class MailAccount(document_models.ModelWithOwner):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
refresh_token = models.CharField(
|
||||||
|
_("refresh token"),
|
||||||
|
max_length=2048,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
help_text=_(
|
||||||
|
"The refresh token to use for token authentication e.g. with oauth2.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expiration = models.DateTimeField(
|
||||||
|
_("expiration"),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
help_text=_(
|
||||||
|
"The expiration date of the refresh token. ",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user