From ace1e1aebedfb5aef9e6845be3c3187f554d9ab7 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Wed, 27 Nov 2024 00:57:51 +0200 Subject: [PATCH] Add check credentials view for Facebook and Instagram channels --- temba/channels/types/facebookapp/type.py | 32 ++++++++++++++++--- temba/channels/types/facebookapp/views.py | 22 ++++++++++++- temba/channels/types/instagram/type.py | 32 ++++++++++++++++--- temba/channels/types/instagram/views.py | 24 ++++++++++++-- temba/channels/types/whatsapp/type.py | 4 +-- temba/channels/views.py | 5 +-- .../types/facebookapp/check_credentials.html | 18 +++++++++++ .../types/instagram/check_credentials.html | 18 +++++++++++ 8 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 templates/channels/types/facebookapp/check_credentials.html create mode 100644 templates/channels/types/instagram/check_credentials.html diff --git a/temba/channels/types/facebookapp/type.py b/temba/channels/types/facebookapp/type.py index 18945cc6d4..4cfecc5adf 100644 --- a/temba/channels/types/facebookapp/type.py +++ b/temba/channels/types/facebookapp/type.py @@ -1,3 +1,4 @@ +from django.urls import re_path import requests from django.conf import settings @@ -7,7 +8,7 @@ from temba.triggers.models import Trigger from ...models import Channel, ChannelType -from .views import ClaimView +from .views import ClaimView, CheckCredentials class FacebookAppType(ChannelType): @@ -32,9 +33,17 @@ class FacebookAppType(ChannelType): ) % {"link": 'Facebook'} claim_view = ClaimView - menu_items = [ - dict(label=_("Reconnect Facebook Page"), view_name="channels.types.facebookapp.claim", obj_view=False) - ] + menu_items = [dict(label=_("Check Credentials"), view_name="channels.types.facebookapp.check_credentials")] + + def get_urls(self): + return [ + self.get_claim_url(), + re_path( + r"^(?P[a-z0-9\-]+)/check_credentials/$", + CheckCredentials.as_view(channel_type=self), + name="check_credentials", + ), + ] def deactivate(self, channel): config = channel.config @@ -81,3 +90,18 @@ def get_redact_values(self, channel) -> tuple: # pragma: needs cover def get_error_ref_url(self, channel, code: str) -> str: return "https://developers.facebook.com/docs/messenger-platform/error-codes" + + def check_credentials(self, config: dict) -> bool: + return False + app_id = settings.FACEBOOK_APPLICATION_ID + app_secret = settings.FACEBOOK_APPLICATION_SECRET + url = "https://graph.facebook.com/v18.0/debug_token" + params = { + "access_token": f"{app_id}|{app_secret}", + "input_token": config[Channel.CONFIG_AUTH_TOKEN], + } + resp = requests.get(url, params=params) + + if resp.status_code == 200: + return resp.json().get("data", dict()).get("is_valid", False) + return False diff --git a/temba/channels/types/facebookapp/views.py b/temba/channels/types/facebookapp/views.py index f0afac43f8..190c95f7ca 100644 --- a/temba/channels/types/facebookapp/views.py +++ b/temba/channels/types/facebookapp/views.py @@ -6,10 +6,11 @@ from django.urls import reverse from django.utils.translation import gettext_lazy as _ +from temba.orgs.views.base import BaseReadView from temba.utils.text import truncate from ...models import Channel -from ...views import ClaimViewMixin +from ...views import ClaimViewMixin, ChannelTypeMixin class ClaimView(ClaimViewMixin, SmartFormView): @@ -141,3 +142,22 @@ def form_valid(self, form): ) return super().form_valid(form) + + +class CheckCredentials(ChannelTypeMixin, BaseReadView): + slug_url_kwarg = "uuid" + permission = "channels.channel_claim" + fields = () + template_name = "channels/types/facebookapp/check_credentials.html" + + def derive_menu_path(self): + return f"/settings/channels/{self.get_object().uuid}" + + def get_queryset(self): + return self.request.org.channels.filter(is_active=True, channel_type=self.channel_type.code) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["update_token_url"] = reverse("channels.types.facebookapp.claim") + context["valid_token"] = self.object.type.check_credentials(self.object.config) + return context diff --git a/temba/channels/types/instagram/type.py b/temba/channels/types/instagram/type.py index 1803c146b6..8407f42ac0 100644 --- a/temba/channels/types/instagram/type.py +++ b/temba/channels/types/instagram/type.py @@ -1,3 +1,4 @@ +from django.urls import re_path import requests from django.conf import settings @@ -6,7 +7,7 @@ from temba.contacts.models import URN from ...models import Channel, ChannelType -from .views import ClaimView +from .views import CheckCredentials, ClaimView class InstagramType(ChannelType): @@ -29,9 +30,17 @@ class InstagramType(ChannelType): } claim_view = ClaimView - menu_items = [ - dict(label=_("Reconnect Business Account"), view_name="channels.types.instagram.claim", obj_view=False) - ] + menu_items = [dict(label=_("Reconnect Business Account"), view_name="channels.types.instagram.check_credentials")] + + def get_urls(self): + return [ + self.get_claim_url(), + re_path( + r"^(?P[a-z0-9\-]+)/check_credentials/$", + CheckCredentials.as_view(channel_type=self), + name="check_credentials", + ), + ] def deactivate(self, channel): config = channel.config @@ -48,3 +57,18 @@ def get_redact_values(self, channel) -> tuple: # pragma: needs cover def get_error_ref_url(self, channel, code: str) -> str: return "https://developers.facebook.com/docs/instagram-api/reference/error-codes" + + def check_credentials(self, config: dict) -> bool: + return True + app_id = settings.FACEBOOK_APPLICATION_ID + app_secret = settings.FACEBOOK_APPLICATION_SECRET + url = "https://graph.facebook.com/v18.0/debug_token" + params = { + "access_token": f"{app_id}|{app_secret}", + "input_token": config[Channel.CONFIG_AUTH_TOKEN], + } + resp = requests.get(url, params=params) + + if resp.status_code == 200: + return resp.json().get("data", dict()).get("is_valid", False) + return False diff --git a/temba/channels/types/instagram/views.py b/temba/channels/types/instagram/views.py index 2d16477921..a53661fd5c 100644 --- a/temba/channels/types/instagram/views.py +++ b/temba/channels/types/instagram/views.py @@ -1,17 +1,18 @@ import logging import requests -from smartmin.views import SmartFormView +from smartmin.views import SmartFormView, SmartReadView from django import forms from django.conf import settings from django.urls import reverse from django.utils.translation import gettext_lazy as _ +from temba.orgs.views.mixins import OrgObjPermsMixin from temba.utils.text import truncate from ...models import Channel -from ...views import ClaimViewMixin +from ...views import ClaimViewMixin, ChannelTypeMixin logger = logging.getLogger(__name__) @@ -172,3 +173,22 @@ def form_valid(self, form): ) return super().form_valid(form) + + +class CheckCredentials(ChannelTypeMixin, OrgObjPermsMixin, SmartReadView): + slug_url_kwarg = "uuid" + permission = "channels.channel_claim" + fields = () + template_name = "channels/types/instagram/check_credentials.html" + + def derive_menu_path(self): + return f"/settings/channels/{self.get_object().uuid}" + + def get_queryset(self): + return self.request.org.channels.filter(is_active=True, channel_type=self.channel_type.code) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["update_token_url"] = reverse("channels.types.instagram.claim") + context["valid_token"] = self.object.type.check_credentials(self.object.config) + return context diff --git a/temba/channels/types/whatsapp/type.py b/temba/channels/types/whatsapp/type.py index e94211672a..01b8b37916 100644 --- a/temba/channels/types/whatsapp/type.py +++ b/temba/channels/types/whatsapp/type.py @@ -35,9 +35,7 @@ class WhatsAppType(ChannelType): claim_blurb = _("If you have an enterprise WhatsApp account, you can connect it to communicate with your contacts") claim_view = ClaimView - menu_items = [ - dict(label=_("Verify Number"), view_name="channels.types.whatsapp.request_code", obj_view=True), - ] + menu_items = [dict(label=_("Verify Number"), view_name="channels.types.whatsapp.request_code")] def get_urls(self): return [ diff --git a/temba/channels/views.py b/temba/channels/views.py index 2b11b97712..468de1d793 100644 --- a/temba/channels/views.py +++ b/temba/channels/views.py @@ -489,10 +489,7 @@ def build_context_menu(self, menu): obj = self.get_object() for item in obj.type.menu_items: - menu.add_link( - item["label"], - reverse(item["view_name"], args=[obj.uuid]) if item["obj_view"] else reverse(item["view_name"]), - ) + menu.add_link(item["label"], reverse(item["view_name"], args=[obj.uuid])) if obj.type.config_ui: menu.add_link(_("Configuration"), reverse("channels.channel_configuration", args=[obj.uuid])) diff --git a/templates/channels/types/facebookapp/check_credentials.html b/templates/channels/types/facebookapp/check_credentials.html new file mode 100644 index 0000000000..2aae8233be --- /dev/null +++ b/templates/channels/types/facebookapp/check_credentials.html @@ -0,0 +1,18 @@ +{% extends "smartmin/read.html" %} +{% load smartmin temba humanize channels i18n tz %} + +{% block content %} +
+

+ {% if not valid_token %} + {% trans "Error with token, you need to reconnect the Facebook page by clicking the button below" %} + {% else %} + {% trans "Everything looks good. No need to reconnect" %} + {% endif %} +

+
+
{% trans "Go Back" %}
+ {% trans "Reconnect Facebook page" %} +
+
+{% endblock content %} diff --git a/templates/channels/types/instagram/check_credentials.html b/templates/channels/types/instagram/check_credentials.html new file mode 100644 index 0000000000..89bafe7ab2 --- /dev/null +++ b/templates/channels/types/instagram/check_credentials.html @@ -0,0 +1,18 @@ +{% extends "smartmin/read.html" %} +{% load smartmin temba humanize channels i18n tz %} + +{% block content %} +
+

+ {% if not valid_token %} + {% trans "Error with token, you need to reconnect the Instagram Business Account by clicking the button below" %} + {% else %} + {% trans "Everything looks good. No need to reconnect" %} + {% endif %} +

+ +
+{% endblock content %}