-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ChannelType.check_credentials
and use for Twilio claims and updates
#4275
Conversation
Codecov Report
@@ Coverage Diff @@
## main #4275 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 472 472
Lines 26130 26178 +48
=========================================
+ Hits 26130 26178 +48
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
temba/channels/models.py
Outdated
@@ -141,6 +141,9 @@ def get_update_form(self): | |||
return UpdateChannelForm | |||
return self.update_form | |||
|
|||
def check_credentials(self, cleaned_data): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want something more like...
def check_credentials(self, config: dict) -> bool:
so then we have something like this on Channel
def check_credentials(self) -> bool:
return self.type.check_credentials(self.config)
and then at any time you can verify the credentials of a channel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
|
||
self.add_config_field( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I exatended the channel update to include the credentials field for for the channel type
temba/channels/types/twilio/views.py
Outdated
def clean(self) -> Dict[str, Any]: | ||
account_sid = self.cleaned_data.get("account_sid", None) | ||
account_token = self.cleaned_data.get("account_token", None) | ||
|
||
try: | ||
client = TwilioClient(account_sid, account_token) | ||
|
||
# get the actual primary auth tokens from twilio and use them | ||
account = client.api.account.fetch() | ||
self.cleaned_data["account_sid"] = account.sid | ||
self.cleaned_data["account_token"] = account.auth_token | ||
except Exception: # pragma: needs cover | ||
raise ValidationError( | ||
_("The Twilio account SID and Token seem invalid. Please check them again and retry.") | ||
) | ||
|
||
return self.cleaned_data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to clean the credentials, and in case of Twilio we need to save the primary token of the account in case we were given others
So each channel type update form will do its own validation if needed
temba/channels/models.py
Outdated
@@ -74,6 +74,7 @@ class IVRProtocol(Enum): | |||
show_public_addresses = False | |||
|
|||
update_form = None | |||
verify_credentials = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now we'll be switching this flag once we have added the channel type check_credentials
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we just have ChannelType.check_credentials
default to True
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we'll remove the flag once the method is implemented for all channel type we have but I guess that is fine to have that default to True
temba/channels/types/twilio/views.py
Outdated
"account_sid", | ||
forms.CharField( | ||
max_length=128, | ||
label=_("Your Twilio Account SID"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove Your
temba/channels/types/twilio/views.py
Outdated
"auth_token", | ||
forms.CharField( | ||
max_length=128, | ||
label=_("Your Twilio Account Auth Token"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove Your
account_sid = self.cleaned_data.get("account_sid", None) | ||
account_token = self.cleaned_data.get("account_token", None) | ||
|
||
try: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want to use check_credentials
here as well - calling it with the config + these new values, e.g.
self.instance.check_credentials({**self.instance.config, "account_sid": account_sid, "auth_token": auth_token})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the main thing here is we want to grab the primary credentials so check_credentials
is not enough alone
otherwise we would have relied on the base class clean from that use the method that way
temba/channels/views.py
Outdated
@@ -670,6 +671,13 @@ def add_config_field(self, config_key: str, field, *, default): | |||
self.fields[config_key] = field | |||
self.config_fields.append(config_key) | |||
|
|||
def clean(self) -> Dict[str, Any]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
python 3.10 prefers dict[str, Any]
.. we need to come up with a style guide for typing in Python. I've generally avoided complex types and probably would have just used dict
here, but we need some rules to keep ourselves consistent.
temba/channels/models.py
Outdated
@@ -74,6 +74,7 @@ class IVRProtocol(Enum): | |||
show_public_addresses = False | |||
|
|||
update_form = None | |||
verify_credentials = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we just have ChannelType.check_credentials
default to True
?
ChannelType.check_credentials
and use for Twilio claims and updates
temba/channels/views.py
Outdated
cleaned_data = super().clean() | ||
channel_type = Channel.get_type_from_code(self.object.channel_type) | ||
if channel_type.verify_credentials and not channel_type.check_credentials(cleaned_data): | ||
raise ValidationError(_("Error checking credentials for channel type %s" % self.object.channel_type)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a user facing error message like Channel credentials don't appear to be valid.
6ad473c
to
d38c66f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@norkans7 i don't know this area of the product super well, so just dropping a comment (vs approving) after reviewing the changes to say LGTM
@rowanseymour @ericnewcomer when you get a chance please review |
) | ||
|
||
# We override the clean method for Twilio we need to make sure we grab the primary auth tokens | ||
def clean(self) -> Dict[str, Any]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def clean(self) -> dict[str, Any]:
"""
We override the clean method for Twilio we need to make sure we grab the primary auth tokens
"""
temba/channels/views.py
Outdated
@@ -424,6 +425,18 @@ def add_config_field(self, config_key: str, field, *, default): | |||
self.fields[config_key] = field | |||
self.config_fields.append(config_key) | |||
|
|||
def transform_form_data_credentials_config(self, cleaned_data: dict, extra: dict) -> dict: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't need to be a separate method.. or have the longest name in the repo lol
temba/channels/views.py
Outdated
|
||
def clean(self) -> dict[str, Any]: | ||
cleaned_data = super().clean() | ||
credentials_config = self.creds_config(cleaned_data, {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am still confused by this line which is just cloning cleaned_data
Shouldn't this be self.object.config | cleaned_data
? i.e. merge existing config and new values from form?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that channel types subclass this and override creds_config
?
In which case make it something like...
def to_config(self, data) -> dict:
return {}
That just returns form data transformed into config values, and do the merging in clean
and form_valid
with self.instance.config | self.to_config(cleaned_data)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right the idea is to allow the subclass to override that in case the form fields names do not match the config keys.
For Twilio so far added I see I had changed to have them match so I think the to_config
is probably not needed for now.
The keys are not the same
…On Wed, Mar 8, 2023, 8:00 PM Rowan Seymour ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In temba/channels/views.py
<#4275 (comment)>:
> @@ -424,6 +425,21 @@ def add_config_field(self, config_key: str, field, *, default):
self.fields[config_key] = field
self.config_fields.append(config_key)
+ def creds_config(self, cleaned_data: dict, extra: dict) -> dict:
+ """
+ Transform form data credentials to config format dict
+ """
+ config = cleaned_data.copy()
+ config.update(**extra)
+ return config
+
+ def clean(self) -> dict[str, Any]:
+ cleaned_data = super().clean()
+ credentials_config = self.creds_config(cleaned_data, {})
Am still confused by this line which is just cloning cleaned_data
Shouldn't this be self.object.config | cleaned_data ? i.e. merge existing
config and new values from form?
—
Reply to this email directly, view it on GitHub
<#4275 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAH6BO2HQD3N4U5UICIAWMDW3DCL5ANCNFSM6AAAAAAULGCU4E>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Addresses #4227
Old UI
New UI