[ADD] OUTLOOK: Add outlook mail integration to Horilla
This commit is contained in:
32
outlook_auth/__init__.py
Normal file
32
outlook_auth/__init__.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
outlook_auth/__init__.py
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from outlook_auth import scheduler as _scheduler
|
||||
|
||||
|
||||
settings.OUTLOOK_SCOPES = ["https://outlook.office.com/SMTP.Send"]
|
||||
|
||||
# Add these in horilla/settings.py
|
||||
|
||||
"""
|
||||
|
||||
installed_apps = [
|
||||
...
|
||||
'outlook_auth',
|
||||
...
|
||||
]
|
||||
|
||||
EMAIL_BACKEND = 'outlook_auth.backends.OutlookBackend'
|
||||
|
||||
"""
|
||||
# NOTE: Horilla should be run in https
|
||||
|
||||
# Please add redircet url in Azure app authentication URi and AzureApi model redirect URi
|
||||
|
||||
"""
|
||||
|
||||
https://<your_domain.com>/outlook/callback
|
||||
|
||||
"""
|
||||
16
outlook_auth/admin.py
Normal file
16
outlook_auth/admin.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
outlook_auth/admin.py
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from outlook_auth import models
|
||||
|
||||
|
||||
# Register your models here.
|
||||
|
||||
|
||||
admin.site.register(
|
||||
[
|
||||
models.AzureApi,
|
||||
]
|
||||
)
|
||||
14
outlook_auth/apps.py
Normal file
14
outlook_auth/apps.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OutlookAuthConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "outlook_auth"
|
||||
|
||||
def ready(self):
|
||||
from horilla.urls import urlpatterns,path,include
|
||||
|
||||
urlpatterns.append(
|
||||
path("outlook/", include("outlook_auth.urls")),
|
||||
)
|
||||
return super().ready()
|
||||
173
outlook_auth/backends.py
Normal file
173
outlook_auth/backends.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
outlook_auth/backeds.py
|
||||
"""
|
||||
|
||||
import base64
|
||||
import logging
|
||||
from django.core.mail import EmailMessage
|
||||
from django.core.mail.backends.smtp import EmailBackend
|
||||
from base.models import EmailLog
|
||||
from horilla.horilla_middlewares import _thread_locals
|
||||
from outlook_auth import models
|
||||
from outlook_auth.views import send_outlook_email
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OutlookBackend(EmailBackend):
|
||||
"""
|
||||
OutlookBackend
|
||||
"""
|
||||
|
||||
api: models.AzureApi = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
self.request = request
|
||||
company = None
|
||||
if request and not request.user.is_anonymous:
|
||||
company = request.user.employee_get.get_company()
|
||||
api = models.AzureApi.objects.filter(company=company).first()
|
||||
if api is None:
|
||||
api = models.AzureApi.objects.filter(is_primary=True).first()
|
||||
self.api = api
|
||||
|
||||
def send_messages(self, email_messages):
|
||||
response = super().send_messages(email_messages)
|
||||
for message in email_messages:
|
||||
email_log = EmailLog(
|
||||
subject=message.subject,
|
||||
from_email=self.dynamic_from_email_with_display_name,
|
||||
to=message.to,
|
||||
body=message.body,
|
||||
status="sent" if response else "failed",
|
||||
)
|
||||
email_log.save()
|
||||
return
|
||||
|
||||
@property
|
||||
def dynamic_from_email_with_display_name(self):
|
||||
if not self.request.user:
|
||||
return f"{self.api.outlook_display_name}<{self.api.outlook_email}>"
|
||||
employee = self.request.user.employee_get
|
||||
full_name = employee.get_full_name()
|
||||
return f"{full_name} <{employee.get_email()}>"
|
||||
|
||||
|
||||
actual_init = EmailMessage.__init__
|
||||
|
||||
|
||||
def __init__(
|
||||
self: EmailMessage,
|
||||
subject="",
|
||||
body="",
|
||||
from_email=None,
|
||||
to=[],
|
||||
bcc=[],
|
||||
connection=None,
|
||||
attachments=None,
|
||||
headers=None,
|
||||
cc=[],
|
||||
reply_to=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
custom __init_method to override
|
||||
"""
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
self.request = request
|
||||
|
||||
if request:
|
||||
try:
|
||||
display_email_name = f"{request.user.employee_get.get_full_name()} <{request.user.employee_get.email}>"
|
||||
from_email = display_email_name if not from_email else from_email
|
||||
reply_to = [display_email_name] if not reply_to else reply_to
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
self.subject = subject
|
||||
self.body = body
|
||||
self.from_email = from_email
|
||||
self.to = to
|
||||
self.cc = cc
|
||||
self.bcc = bcc
|
||||
self.attachments = attachments
|
||||
self.headers = headers
|
||||
self.reply_to = reply_to
|
||||
|
||||
actual_init(
|
||||
self,
|
||||
subject=subject,
|
||||
body=body,
|
||||
from_email=from_email,
|
||||
to=to,
|
||||
bcc=bcc,
|
||||
connection=connection,
|
||||
attachments=attachments,
|
||||
headers=headers,
|
||||
cc=cc,
|
||||
reply_to=reply_to,
|
||||
)
|
||||
|
||||
# Prepare email data for Outlook API
|
||||
|
||||
|
||||
def send_mail(self, *args, **kwargs):
|
||||
"""
|
||||
Sent mail
|
||||
"""
|
||||
|
||||
self.email_data = {
|
||||
"message": {
|
||||
"subject": self.subject,
|
||||
"body": {
|
||||
"contentType": "HTML" if self.content_subtype == "html" else "Text",
|
||||
"content": self.body,
|
||||
},
|
||||
"toRecipients": [
|
||||
{"emailAddress": {"address": recipient}} for recipient in self.to
|
||||
],
|
||||
"ccRecipients": [
|
||||
{"emailAddress": {"address": recipient}} for recipient in self.cc
|
||||
],
|
||||
"bccRecipients": [
|
||||
{"emailAddress": {"address": recipient}} for recipient in self.bcc
|
||||
],
|
||||
},
|
||||
}
|
||||
if self.request and not self.request.user.is_anonymous:
|
||||
reply_to = self.request.user.employee_get.get_email()
|
||||
self.email_data["message"]["replyTo"] = []
|
||||
self.email_data["message"]["replyTo"].append(
|
||||
{"emailAddress": {"address": reply_to}}
|
||||
)
|
||||
|
||||
if self.attachments:
|
||||
outlook_attachments = []
|
||||
for attachment in self.attachments:
|
||||
if isinstance(attachment, tuple):
|
||||
filename, content, mimetype = attachment
|
||||
if hasattr(content, "read"):
|
||||
content = content.read()
|
||||
|
||||
# Encode contentBytes using base64
|
||||
if isinstance(content, bytes):
|
||||
content_bytes = base64.b64encode(content).decode("utf-8")
|
||||
else:
|
||||
content_bytes = content
|
||||
|
||||
outlook_attachments.append(
|
||||
{
|
||||
"@odata.type": "#microsoft.graph.fileAttachment",
|
||||
"name": filename,
|
||||
"contentType": mimetype,
|
||||
"contentBytes": content_bytes,
|
||||
}
|
||||
)
|
||||
self.email_data["message"]["attachments"] = outlook_attachments
|
||||
send_outlook_email(self.request, self.email_data)
|
||||
|
||||
|
||||
EmailMessage.__init__ = __init__
|
||||
EmailMessage.send = send_mail
|
||||
101
outlook_auth/cbv/views.py
Normal file
101
outlook_auth/cbv/views.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""
|
||||
outlook_auth/cbv.py
|
||||
|
||||
"""
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.contrib import messages
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.decorators import method_decorator
|
||||
from horilla_views.generic.cbv import views
|
||||
from horilla_views.cbv_methods import login_required, permission_required
|
||||
from outlook_auth import models, filters, forms
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
permission_required(perm="outlook_auth.view_azureapi"), name="dispatch"
|
||||
)
|
||||
class ServerNav(views.HorillaNavView):
|
||||
"""
|
||||
ServerList
|
||||
"""
|
||||
|
||||
model = models.AzureApi
|
||||
search_url = reverse_lazy("outlook_server_list")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.create_attrs = f"""
|
||||
onclick = "event.stopPropagation();"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-target="#genericModalBody"
|
||||
hx-get="{reverse_lazy('outlook_server_create')}"
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
|
||||
nav_title = _("Mail Servers")
|
||||
filter_instance = filters.AzureApiFilter()
|
||||
search_swap_target = "#listContainer"
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
permission_required(perm="outlook_auth.view_azureapi"), name="dispatch"
|
||||
)
|
||||
class ServerList(views.HorillaListView):
|
||||
"""
|
||||
ServerList
|
||||
"""
|
||||
|
||||
model = models.AzureApi
|
||||
view_id = "listContainer"
|
||||
columns = [
|
||||
(_("Name"), "outlook_display_name"),
|
||||
(_("Email"), "outlook_email"),
|
||||
(_("Company"), "company"),
|
||||
(_("Token Expire"), "token_expire"),
|
||||
(_("Primary"), "is_primary"),
|
||||
]
|
||||
show_filter_tags = False
|
||||
filter_class = filters.AzureApiFilter
|
||||
search_url = reverse_lazy("outlook_server_list")
|
||||
action_method = "actions"
|
||||
selected_instances_key_id = "selectedRecords"
|
||||
header_attrs = {
|
||||
"action": """
|
||||
style = "width:298px !important"
|
||||
""",
|
||||
}
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
permission_required(perm="outlook_auth.add_azureapi"), name="dispatch"
|
||||
)
|
||||
class ServerForm(views.HorillaFormView):
|
||||
"""
|
||||
ServerForm
|
||||
"""
|
||||
|
||||
model = models.AzureApi
|
||||
form_class = forms.OutlookServerForm
|
||||
new_display_title = _("Create Mail Server")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if self.form.instance.pk:
|
||||
self.form_class.verbose_name = _("Update Mail Server")
|
||||
return context
|
||||
|
||||
def form_valid(self, form: forms.OutlookServerForm) -> HttpResponse:
|
||||
|
||||
if form.is_valid():
|
||||
if form.instance.pk:
|
||||
message = _("Mail server updated successfully.")
|
||||
else:
|
||||
message = _("Mail server created successfully.")
|
||||
form.save()
|
||||
messages.success(self.request, _(message))
|
||||
return self.HttpResponse()
|
||||
return super().form_valid(form)
|
||||
25
outlook_auth/filters.py
Normal file
25
outlook_auth/filters.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
outlook_auth/filters.py
|
||||
"""
|
||||
|
||||
import django_filters
|
||||
from outlook_auth import models
|
||||
|
||||
|
||||
class AzureApiFilter(django_filters.FilterSet):
|
||||
"""
|
||||
AzureApiFilter
|
||||
"""
|
||||
|
||||
search = django_filters.CharFilter(
|
||||
field_name="outlook_email", lookup_expr="icontains"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class for additional options"""
|
||||
|
||||
fields = [
|
||||
"search",
|
||||
]
|
||||
model = models.AzureApi
|
||||
20
outlook_auth/forms.py
Normal file
20
outlook_auth/forms.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
outlook_auth/forms.py
|
||||
"""
|
||||
|
||||
from base.forms import ModelForm
|
||||
from outlook_auth import models
|
||||
|
||||
|
||||
class OutlookServerForm(ModelForm):
|
||||
"""
|
||||
OutlookServerForm
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta
|
||||
"""
|
||||
|
||||
model = models.AzureApi
|
||||
fields = "__all__"
|
||||
16
outlook_auth/methods.py
Normal file
16
outlook_auth/methods.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
outlook_auth/methods.py
|
||||
"""
|
||||
|
||||
|
||||
def sec_to_hm(seconds):
|
||||
"""
|
||||
this method is used to formate seconds to H:M and return it
|
||||
args:
|
||||
seconds : seconds
|
||||
"""
|
||||
|
||||
hour = int(seconds // 3600)
|
||||
minutes = int((seconds % 3600) // 60)
|
||||
seconds = int((seconds % 3600) % 60)
|
||||
return f"{hour:02d}:{minutes:02d}"
|
||||
0
outlook_auth/migrations/__init__.py
Normal file
0
outlook_auth/migrations/__init__.py
Normal file
74
outlook_auth/models.py
Normal file
74
outlook_auth/models.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
outlook_auth/models.py
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from base.models import Company
|
||||
|
||||
from horilla_views.cbv_methods import render_template
|
||||
from outlook_auth.methods import sec_to_hm
|
||||
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class AzureApi(models.Model):
|
||||
"""
|
||||
AzureApi
|
||||
"""
|
||||
|
||||
outlook_client_id = models.CharField(max_length=200, verbose_name=_("Client ID"))
|
||||
outlook_client_secret = models.CharField(
|
||||
max_length=200, verbose_name=_("Client Secret")
|
||||
)
|
||||
outlook_tenant_id = models.CharField(max_length=200, verbose_name=_("Tenant ID"))
|
||||
outlook_email = models.EmailField(verbose_name=_("Email"))
|
||||
outlook_display_name = models.CharField(
|
||||
max_length=25, verbose_name=_("Display Name")
|
||||
)
|
||||
outlook_redirect_uri = models.URLField(verbose_name=("Redirect URi"))
|
||||
outlook_authorization_url = models.URLField(
|
||||
default="https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
||||
verbose_name="OAuth authorization endpoint",
|
||||
)
|
||||
outlook_token_url = models.URLField(
|
||||
default="https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
verbose_name="OAuth token endpoint",
|
||||
)
|
||||
outlook_api_endpoint = models.URLField(
|
||||
default="https://graph.microsoft.com/v1.0",
|
||||
verbose_name="Microsoft Graph API endpoint",
|
||||
)
|
||||
company = models.ForeignKey(Company, on_delete=models.CASCADE)
|
||||
is_primary = models.BooleanField(default=False)
|
||||
token = models.JSONField(editable=False, default=dict)
|
||||
oauth_state = models.CharField(editable=False, max_length=100, null=True)
|
||||
last_refreshed = models.DateTimeField(null=True, editable=False)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.is_primary:
|
||||
AzureApi.objects.filter(is_primary=True).update(is_primary=False)
|
||||
elif not AzureApi.objects.filter(is_primary=True).first():
|
||||
self.is_primary = True
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.outlook_display_name} <{self.outlook_email}>"
|
||||
|
||||
def actions(self):
|
||||
return render_template("outlook/actions.html", {"instance": self})
|
||||
|
||||
def token_expire(self):
|
||||
"""
|
||||
last authenticated
|
||||
"""
|
||||
|
||||
if self.last_refreshed:
|
||||
duration_seconds = (timezone.now() - self.last_refreshed).seconds
|
||||
display = sec_to_hm(duration_seconds)
|
||||
expires_in_seconds = self.token.get("expires_in")
|
||||
if duration_seconds > expires_in_seconds:
|
||||
return _("Expired⚠️")
|
||||
return f"{display}/{sec_to_hm(expires_in_seconds)}"
|
||||
41
outlook_auth/scheduler.py
Normal file
41
outlook_auth/scheduler.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
outlook_auth/scheduler.py
|
||||
|
||||
"""
|
||||
|
||||
import sys, logging
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def refresh_outlook_auth_token():
|
||||
"""
|
||||
scheduler method to refresh token
|
||||
"""
|
||||
from outlook_auth.views import refresh_outlook_token
|
||||
from outlook_auth.models import AzureApi
|
||||
|
||||
apis = AzureApi.objects.filter(token__isnull=False)
|
||||
for api in apis:
|
||||
try:
|
||||
refresh_outlook_token(api)
|
||||
logger.info(f"Updated token for {api} outlook auth")
|
||||
print(f"Updated token for {api} outlook auth")
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
|
||||
if not any(
|
||||
cmd in sys.argv
|
||||
for cmd in ["makemigrations", "migrate", "compilemessages", "flush", "shell"]
|
||||
):
|
||||
scheduler = BackgroundScheduler()
|
||||
scheduler.add_job(
|
||||
refresh_outlook_auth_token,
|
||||
"interval",
|
||||
minutes=30,
|
||||
id="refresh_outlook_auth_token",
|
||||
)
|
||||
scheduler.start()
|
||||
|
||||
16
outlook_auth/templates/outlook/actions.html
Normal file
16
outlook_auth/templates/outlook/actions.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.outlook_auth.change_azureapi and instance.token %}
|
||||
<a href="{% url 'refresh_outlook_token' instance.pk %}" class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Refresh Token' %}"><ion-icon name="refresh"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.outlook_auth.change_azureapi %}
|
||||
<a target="_blank" href="/outlook/login/" class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Login' %}"><ion-icon name="log-in-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
<a hx-get="{% url 'mail-server-test-email' %}?instance_id={{ instance.id }}" onclick="event.stopPropagation()" hx-target="#mailServerModalBody" data-toggle="oh-modal-toggle" data-target="#mailServerModal" class="oh-btn oh-btn--light-bkg w-100" title="Test email"><ion-icon name="mail-unread-outline" role="img" class="md hydrated" aria-label="at-circle-outline"></ion-icon></a>
|
||||
{% if perms.outlook_auth.change_azureapi %}
|
||||
<a hx-get="{% url 'outlook_server_change' instance.id %}?instance_ids={{ instance.ordered_ids }}" onclick="event.stopPropagation()" hx-target="#genericModalBody" data-toggle="oh-modal-toggle" data-target="#genericModal" class="oh-btn oh-btn--light-bkg w-100" title="Edit"><ion-icon name="create-outline" role="img" class="md hydrated" aria-label="create outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.outlook_auth.delete_azureapi %}
|
||||
<a onclick="event.stopPropagation()" hx-get="{% url 'generic-delete' %}?pk={{instance.pk}}&model=outlook_auth.AzureApi" data-toggle="oh-modal-toggle" data-target="#deleteConfirmation" hx-target="#deleteConfirmationBody" type="submit" class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" title="Remove"><ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="trash outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
21
outlook_auth/templates/outlook/view_records.html
Normal file
21
outlook_auth/templates/outlook/view_records.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends "settings.html" %}
|
||||
{% block settings %}
|
||||
{% load i18n %}
|
||||
{% include "generic/components.html" %}
|
||||
<div class="oh-modal" id="mailServerModal" role="dialog" aria-labelledby="mailServerModal" aria-hidden="true">
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="editModal1ModalLabel">
|
||||
{% trans "Mail Server" %}
|
||||
</h2>
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body" id="mailServerModalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: none;" data-ids="[]" id="selectedRecords"></div>
|
||||
<div hx-get="{% url "outlook_server_nav" %}" hx-trigger="load"></div>
|
||||
<div class="oh-wrapper" hx-get="{% url "outlook_server_list" %}" hx-trigger="load"></div>
|
||||
{% endblock settings %}
|
||||
3
outlook_auth/tests.py
Normal file
3
outlook_auth/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
28
outlook_auth/urls.py
Normal file
28
outlook_auth/urls.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
outlook_auth/urls.py
|
||||
"""
|
||||
|
||||
from django.urls import path
|
||||
from outlook_auth import views
|
||||
from outlook_auth.cbv import views as cbv
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("login/", views.outlook_login, name="outlook_login"),
|
||||
path("refresh/<int:pk>/", views.refresh_token, name="refresh_outlook_token"),
|
||||
path("callback/", views.outlook_callback, name="outlook_callback"),
|
||||
path("send_email/", views.send_outlook_email, name="send_email"),
|
||||
path(
|
||||
"view-outlook-servers/", views.view_outlook_records, name="outlook_view_records"
|
||||
),
|
||||
path("outlook-server-nav/", cbv.ServerNav.as_view(), name="outlook_server_nav"),
|
||||
path("outlook-server-list/", cbv.ServerList.as_view(), name="outlook_server_list"),
|
||||
path(
|
||||
"outlook-server-form/", cbv.ServerForm.as_view(), name="outlook_server_create"
|
||||
),
|
||||
path(
|
||||
"outlook-server-form/<int:pk>/",
|
||||
cbv.ServerForm.as_view(),
|
||||
name="outlook_server_change",
|
||||
),
|
||||
]
|
||||
170
outlook_auth/views.py
Normal file
170
outlook_auth/views.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
outlook_auth/views.py
|
||||
"""
|
||||
|
||||
from requests_oauthlib import OAuth2Session
|
||||
from datetime import datetime
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect, render
|
||||
from django.core.cache import cache
|
||||
from horilla.decorators import (
|
||||
login_required,
|
||||
permission_required,
|
||||
)
|
||||
from outlook_auth import models
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("outlook_auth.add_azureapi")
|
||||
def outlook_login(request):
|
||||
"""
|
||||
outlook login
|
||||
"""
|
||||
selected_company = request.session.get("selected_company")
|
||||
if not selected_company or selected_company == "all":
|
||||
api = models.AzureApi.objects.filter(is_primary=True).first()
|
||||
else:
|
||||
api = models.AzureApi.objects.filter(company=selected_company).first()
|
||||
|
||||
if not api:
|
||||
messages.info(request, "Not configured outlook")
|
||||
oauth = OAuth2Session(
|
||||
api.outlook_client_id,
|
||||
redirect_uri=api.outlook_redirect_uri,
|
||||
scope=["Mail.Send", "offline_access"],
|
||||
)
|
||||
authorization_url, state = oauth.authorization_url(api.outlook_authorization_url)
|
||||
|
||||
api.oauth_state = state
|
||||
api.save()
|
||||
cache.set("oauth_state", state)
|
||||
return redirect(authorization_url)
|
||||
|
||||
|
||||
def refresh_outlook_token(api: models.AzureApi):
|
||||
"""
|
||||
Refresh Outlook token
|
||||
"""
|
||||
oauth = OAuth2Session(
|
||||
api.outlook_client_id,
|
||||
token=api.token,
|
||||
auto_refresh_kwargs={
|
||||
"client_id": api.outlook_client_id,
|
||||
"client_secret": api.outlook_client_secret,
|
||||
},
|
||||
auto_refresh_url=api.outlook_token_url,
|
||||
)
|
||||
new_token = oauth.refresh_token(
|
||||
api.outlook_token_url,
|
||||
refresh_token=api.token["refresh_token"],
|
||||
client_id=api.outlook_client_id,
|
||||
client_secret=api.outlook_client_secret,
|
||||
)
|
||||
api.token = new_token
|
||||
api.last_refreshed = datetime.now()
|
||||
api.save()
|
||||
return api
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("outlook_auth.change_azureapi")
|
||||
def refresh_token(request, pk, *args, **kwargs):
|
||||
"""
|
||||
outlook_freshe
|
||||
"""
|
||||
api = models.AzureApi.objects.get(pk=pk)
|
||||
old_token = api.token.get("access_token")
|
||||
api = refresh_outlook_token(api)
|
||||
if api.token.get("access_token") == old_token:
|
||||
messages.info(request, _("Token not refreshed, Login required"))
|
||||
else:
|
||||
messages.success(request, _("Token refreshed successfully"))
|
||||
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("outlook_auth.change_azureapi")
|
||||
def outlook_callback(request):
|
||||
"""
|
||||
outlook callback
|
||||
"""
|
||||
selected_company = request.session.get("selected_company")
|
||||
if not selected_company or selected_company == "all":
|
||||
api = models.AzureApi.objects.filter(is_primary=True).first()
|
||||
else:
|
||||
api = models.AzureApi.objects.filter(company=selected_company).first()
|
||||
|
||||
state = api.oauth_state
|
||||
|
||||
oauth = OAuth2Session(
|
||||
api.outlook_client_id,
|
||||
state=state,
|
||||
redirect_uri=api.outlook_redirect_uri,
|
||||
)
|
||||
|
||||
authorization_response_uri = request.build_absolute_uri()
|
||||
authorization_response_uri = authorization_response_uri.replace(
|
||||
"http://", "https://"
|
||||
)
|
||||
api.last_refreshed = datetime.now()
|
||||
token = oauth.fetch_token(
|
||||
api.outlook_token_url,
|
||||
client_secret=api.outlook_client_secret,
|
||||
authorization_response=authorization_response_uri, # Use the modified URI
|
||||
)
|
||||
api.token = token
|
||||
api.save()
|
||||
|
||||
return redirect("/")
|
||||
|
||||
|
||||
def send_outlook_email(request, email_data=None):
|
||||
"""
|
||||
send mail
|
||||
"""
|
||||
selected_company = None
|
||||
if request:
|
||||
selected_company = request.session.get("selected_company")
|
||||
if not selected_company or selected_company == "all":
|
||||
api = models.AzureApi.objects.filter(is_primary=True).first()
|
||||
else:
|
||||
api = models.AzureApi.objects.filter(company=selected_company).first()
|
||||
token = api.token
|
||||
|
||||
refresh_outlook_token(api)
|
||||
if not token and request:
|
||||
messages.info(request, "Mail not sent")
|
||||
return redirect("outlook_login")
|
||||
|
||||
oauth = OAuth2Session(
|
||||
api.outlook_client_id,
|
||||
token=token,
|
||||
auto_refresh_kwargs={
|
||||
"client_id": api.outlook_client_id,
|
||||
"client_secret": api.outlook_client_secret,
|
||||
},
|
||||
auto_refresh_url=api.outlook_token_url,
|
||||
)
|
||||
response = oauth.post(f"{api.outlook_api_endpoint}/me/sendMail", json=email_data)
|
||||
|
||||
try:
|
||||
response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
|
||||
messages.success(request, _("Mail sent"))
|
||||
# Email sent successfully!
|
||||
return email_data
|
||||
except Exception as e:
|
||||
messages.error(_("Something went wrong"))
|
||||
messages.info(_("Outlook authentication required/expired"))
|
||||
return email_data
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("outlook_auth.view_azureapi")
|
||||
def view_outlook_records(request):
|
||||
"""
|
||||
View server records
|
||||
"""
|
||||
return render(request, "outlook/view_records.html")
|
||||
@@ -20,6 +20,7 @@ django-haystack
|
||||
django-import-export
|
||||
django-jsonfield
|
||||
django-mathfilters
|
||||
django-microsoft-auth
|
||||
django-model-utils
|
||||
django-simple-history
|
||||
django-widget-tweaks
|
||||
@@ -34,6 +35,7 @@ Jinja2
|
||||
idna
|
||||
lxml
|
||||
numpy
|
||||
oauthlib
|
||||
openpyxl
|
||||
oscrypto
|
||||
pandas
|
||||
|
||||
@@ -110,16 +110,25 @@
|
||||
>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if perms.base.view_dynamicemailconfiguration and not "outlook_auth"|app_installed %}
|
||||
<div class="oh-input-group">
|
||||
{% if perms.base.view_dynamicemailconfiguration %}
|
||||
<a
|
||||
id="date"
|
||||
href="{% url 'mail-server-conf' %}"
|
||||
class="oh-inner-sidebar__link oh-dropdown__link"
|
||||
>{% trans "Mail Server" %}</a
|
||||
>
|
||||
{% endif %}
|
||||
<a
|
||||
id="date"
|
||||
href="{% url 'mail-server-conf' %}"
|
||||
class="oh-inner-sidebar__link oh-dropdown__link"
|
||||
>{% trans "Mail Server" %}</a
|
||||
>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="oh-input-group">
|
||||
<a
|
||||
id="date"
|
||||
href="{% url 'outlook_view_records' %}"
|
||||
class="oh-inner-sidebar__link oh-dropdown__link"
|
||||
>{% trans "Outlook Mail" %}</a
|
||||
>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="oh-input-group">
|
||||
{% if perms.horilla_backup.view_googledrivebackup %}
|
||||
<a
|
||||
|
||||
Reference in New Issue
Block a user