mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 01:47:35 -02:30
Support notification password field encryption
Modify encrypt_field and decrypt_field to support sub-fields under a dictionary object. It still uses the parent key when encrypting.
This commit is contained in:
@@ -2059,8 +2059,20 @@ class NotifierSerializer(BaseSerializer):
|
|||||||
model = Notifier
|
model = Notifier
|
||||||
fields = ('*', 'organization', 'notification_type', 'notification_configuration')
|
fields = ('*', 'organization', 'notification_type', 'notification_configuration')
|
||||||
|
|
||||||
type_map = {"string": str, "int": int, "bool": bool, "list": list,
|
type_map = {"string": (str, unicode),
|
||||||
"password": str, "object": dict}
|
"int": (int,),
|
||||||
|
"bool": (bool,),
|
||||||
|
"list": (list,),
|
||||||
|
"password": (str, unicode),
|
||||||
|
"object": (dict,)}
|
||||||
|
|
||||||
|
def to_representation(self, obj):
|
||||||
|
ret = super(NotifierSerializer, self).to_representation(obj)
|
||||||
|
for field in obj.notification_class.init_parameters:
|
||||||
|
if field in ret['notification_configuration'] and \
|
||||||
|
force_text(ret['notification_configuration'][field]).startswith('$encrypted$'):
|
||||||
|
ret['notification_configuration'][field] = '$encrypted$'
|
||||||
|
return ret
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = super(NotifierSerializer, self).get_related(obj)
|
res = super(NotifierSerializer, self).get_related(obj)
|
||||||
@@ -2076,20 +2088,28 @@ class NotifierSerializer(BaseSerializer):
|
|||||||
notification_class = Notifier.CLASS_FOR_NOTIFICATION_TYPE[attrs['notification_type']]
|
notification_class = Notifier.CLASS_FOR_NOTIFICATION_TYPE[attrs['notification_type']]
|
||||||
missing_fields = []
|
missing_fields = []
|
||||||
incorrect_type_fields = []
|
incorrect_type_fields = []
|
||||||
|
if 'notification_configuration' not in attrs:
|
||||||
|
return attrs
|
||||||
for field in notification_class.init_parameters:
|
for field in notification_class.init_parameters:
|
||||||
if field not in attrs['notification_configuration']:
|
if field not in attrs['notification_configuration']:
|
||||||
missing_fields.append(field)
|
missing_fields.append(field)
|
||||||
continue
|
continue
|
||||||
field_val = attrs['notification_configuration'][field]
|
field_val = attrs['notification_configuration'][field]
|
||||||
field_type = notification_class.init_parameters[field]['type']
|
field_type = notification_class.init_parameters[field]['type']
|
||||||
expected_type = self.type_map[field_type]
|
expected_types = self.type_map[field_type]
|
||||||
if not isinstance(field_val, expected_type):
|
if not type(field_val) in expected_types:
|
||||||
incorrect_type_fields.append((field, field_type))
|
incorrect_type_fields.append((field, field_type))
|
||||||
|
continue
|
||||||
|
if field_type == "password" and field_val.startswith('$encrypted$'):
|
||||||
|
missing_fields.append(field)
|
||||||
|
error_list = []
|
||||||
if missing_fields:
|
if missing_fields:
|
||||||
error_list = ["Missing required fields for Notification Configuration: {}".format(missing_fields)]
|
error_list.append("Missing required fields for Notification Configuration: {}".format(missing_fields))
|
||||||
|
if incorrect_type_fields:
|
||||||
for type_field_error in incorrect_type_fields:
|
for type_field_error in incorrect_type_fields:
|
||||||
error_list.append("Configuration field {} incorrect type, expected {}".format(type_field_error[0],
|
error_list.append("Configuration field '{}' incorrect type, expected {}".format(type_field_error[0],
|
||||||
type_field_error[1]))
|
type_field_error[1]))
|
||||||
|
if error_list:
|
||||||
raise serializers.ValidationError(error_list)
|
raise serializers.ValidationError(error_list)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
|
|
||||||
from awx.main.models.base import * # noqa
|
from awx.main.models.base import * # noqa
|
||||||
|
from awx.main.utils import encrypt_field, decrypt_field
|
||||||
from awx.main.notifications.email_backend import CustomEmailBackend
|
from awx.main.notifications.email_backend import CustomEmailBackend
|
||||||
from awx.main.notifications.slack_backend import SlackBackend
|
from awx.main.notifications.slack_backend import SlackBackend
|
||||||
from awx.main.notifications.twilio_backend import TwilioBackend
|
from awx.main.notifications.twilio_backend import TwilioBackend
|
||||||
@@ -62,6 +63,31 @@ class Notifier(CommonModel):
|
|||||||
def notification_class(self):
|
def notification_class(self):
|
||||||
return self.CLASS_FOR_NOTIFICATION_TYPE[self.notification_type]
|
return self.CLASS_FOR_NOTIFICATION_TYPE[self.notification_type]
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
new_instance = not bool(self.pk)
|
||||||
|
update_fields = kwargs.get('update_fields', [])
|
||||||
|
for field in filter(lambda x: self.notification_class.init_parameters[x]['type'] == "password",
|
||||||
|
self.notification_class.init_parameters):
|
||||||
|
if new_instance:
|
||||||
|
value = getattr(self.notification_configuration, field, '')
|
||||||
|
setattr(self, '_saved_{}'.format(field), value)
|
||||||
|
self.notification_configuration[field] = ''
|
||||||
|
else:
|
||||||
|
encrypted = encrypt_field(self, 'notification_configuration', subfield=field)
|
||||||
|
self.notification_configuration[field] = encrypted
|
||||||
|
if 'notification_configuration' not in update_fields:
|
||||||
|
update_fields.append('notification_configuration')
|
||||||
|
super(Notifier, self).save(*args, **kwargs)
|
||||||
|
if new_instance:
|
||||||
|
update_fields = []
|
||||||
|
for field in filter(lambda x: self.notification_class.init_parameters[x]['type'] == "password",
|
||||||
|
self.notification_class.init_parameters):
|
||||||
|
saved_value = getattr(self, '_saved_{}'.format(field), '')
|
||||||
|
setattr(self.notification_configuration, field, saved_value)
|
||||||
|
if 'notification_configuration' not in update_fields:
|
||||||
|
update_fields.append('notification_configuration')
|
||||||
|
self.save(update_fields=update_fields)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def recipients(self):
|
def recipients(self):
|
||||||
return self.notification_configuration[self.notification_class.recipient_parameter]
|
return self.notification_configuration[self.notification_class.recipient_parameter]
|
||||||
@@ -76,6 +102,11 @@ class Notifier(CommonModel):
|
|||||||
return notification
|
return notification
|
||||||
|
|
||||||
def send(self, subject, body):
|
def send(self, subject, body):
|
||||||
|
for field in filter(lambda x: self.notification_class.init_parameters[x]['type'] == "password",
|
||||||
|
self.notification_class.init_parameters):
|
||||||
|
self.notification_configuration[field] = decrypt_field(self,
|
||||||
|
'notification_configuration',
|
||||||
|
subfield=field)
|
||||||
recipients = self.notification_configuration.pop(self.notification_class.recipient_parameter)
|
recipients = self.notification_configuration.pop(self.notification_class.recipient_parameter)
|
||||||
if not isinstance(recipients, list):
|
if not isinstance(recipients, list):
|
||||||
recipients = [recipients]
|
recipients = [recipients]
|
||||||
|
|||||||
@@ -139,12 +139,13 @@ def get_encryption_key(instance, field_name):
|
|||||||
h.update(field_name)
|
h.update(field_name)
|
||||||
return h.digest()[:16]
|
return h.digest()[:16]
|
||||||
|
|
||||||
|
def encrypt_field(instance, field_name, ask=False, subfield=None):
|
||||||
def encrypt_field(instance, field_name, ask=False):
|
|
||||||
'''
|
'''
|
||||||
Return content of the given instance and field name encrypted.
|
Return content of the given instance and field name encrypted.
|
||||||
'''
|
'''
|
||||||
value = getattr(instance, field_name)
|
value = getattr(instance, field_name)
|
||||||
|
if isinstance(value, dict) and subfield is not None:
|
||||||
|
value = value[subfield]
|
||||||
if not value or value.startswith('$encrypted$') or (ask and value == 'ASK'):
|
if not value or value.startswith('$encrypted$') or (ask and value == 'ASK'):
|
||||||
return value
|
return value
|
||||||
value = smart_str(value)
|
value = smart_str(value)
|
||||||
@@ -157,11 +158,13 @@ def encrypt_field(instance, field_name, ask=False):
|
|||||||
return '$encrypted$%s$%s' % ('AES', b64data)
|
return '$encrypted$%s$%s' % ('AES', b64data)
|
||||||
|
|
||||||
|
|
||||||
def decrypt_field(instance, field_name):
|
def decrypt_field(instance, field_name, subfield=None):
|
||||||
'''
|
'''
|
||||||
Return content of the given instance and field name decrypted.
|
Return content of the given instance and field name decrypted.
|
||||||
'''
|
'''
|
||||||
value = getattr(instance, field_name)
|
value = getattr(instance, field_name)
|
||||||
|
if isinstance(value, dict) and subfield is not None:
|
||||||
|
value = value[subfield]
|
||||||
if not value or not value.startswith('$encrypted$'):
|
if not value or not value.startswith('$encrypted$'):
|
||||||
return value
|
return value
|
||||||
algo, b64data = value[len('$encrypted$'):].split('$', 1)
|
algo, b64data = value[len('$encrypted$'):].split('$', 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user