Skip to content
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

TableauConnectedApp, TransifexOrganization, and OpenmrsImporter models: AES CBC encryption read and write #35665

Merged
merged 10 commits into from
Jan 24, 2025
16 changes: 13 additions & 3 deletions corehq/apps/reports/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@

from corehq.apps.reports.const import TABLEAU_ROLES
from corehq.apps.users.models import CommCareUser
from corehq.motech.utils import b64_aes_decrypt, b64_aes_encrypt
from corehq.motech.const import ALGO_AES_CBC
from corehq.motech.utils import (
b64_aes_decrypt,
b64_aes_cbc_encrypt,
b64_aes_cbc_decrypt
)


class HQUserType(object):
Expand Down Expand Up @@ -204,11 +209,16 @@ def __str__(self):

@property
def plaintext_secret_value(self):
return b64_aes_decrypt(self.encrypted_secret_value)
# Conditonal check be deleted after migration to cbc is done
if self.encrypted_secret_value.startswith(f'${ALGO_AES_CBC}$'):
ciphertext = self.encrypted_secret_value.split('$', 2)[2]
return b64_aes_cbc_decrypt(ciphertext)
return b64_aes_decrypt(self.encrypted_secret_value) # This will be deleted after migration to cbc is done

@plaintext_secret_value.setter
def plaintext_secret_value(self, plaintext):
self.encrypted_secret_value = b64_aes_encrypt(plaintext)
ciphertext = b64_aes_cbc_encrypt(plaintext)
self.encrypted_secret_value = f'${ALGO_AES_CBC}${ciphertext}'

def create_jwt(self):
connected_app_permissions = ["tableau:users:read", "tableau:users:create", "tableau:users:update",
Expand Down
2 changes: 1 addition & 1 deletion corehq/apps/reports/tests/test_tableau_api_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def test_connected_app_encryption(self):

self.assertNotEqual(self.connected_app.encrypted_secret_value, 'qwer1234')
self.assertEqual(self.connected_app.plaintext_secret_value, 'qwer1234')
self.assertEqual(len(self.connected_app.encrypted_secret_value), 24)
self.assertEqual(len(self.connected_app.encrypted_secret_value), 53)

def _assert_subset(self, d1, d2):
self.assertTrue(set(d1.items()).issubset(set(d2.items())))
Expand Down
3 changes: 1 addition & 2 deletions corehq/apps/translations/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from django.contrib import admin

from corehq.motech.utils import b64_aes_encrypt

from .forms import TransifexOrganizationForm
from .models import TransifexOrganization
Expand All @@ -10,7 +9,7 @@ class TransifexOrganizationAdmin(admin.ModelAdmin):
form = TransifexOrganizationForm

def save_model(self, request, obj, form, change):
obj.api_token = b64_aes_encrypt(obj.api_token)
obj.plaintext_api_token = obj.api_token
super(TransifexOrganizationAdmin, self).save_model(request, obj, form, change)


Expand Down
3 changes: 1 addition & 2 deletions corehq/apps/translations/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
TransifexBlacklist,
TransifexProject,
)
from corehq.motech.utils import b64_aes_decrypt
from corehq.util.workbook_json.excel import WorkbookJSONReader


Expand Down Expand Up @@ -307,7 +306,7 @@ class DownloadAppTranslationsForm(CreateUpdateAppTranslationsForm):
class TransifexOrganizationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TransifexOrganizationForm, self).__init__(*args, **kwargs)
self.initial['api_token'] = b64_aes_decrypt(self.instance.api_token)
self.initial['api_token'] = self.instance.plaintext_api_token


class AddTransifexBlacklistForm(forms.ModelForm):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, domain, project_slug, source_app_id, target_app_id, resource_
self.domain = domain
self.project_slug = project_slug
self.project = TransifexProject.objects.get(slug=project_slug)
self.client = TransifexApiClient(self.project.organization.get_api_token, self.project.organization,
self.client = TransifexApiClient(self.project.organization.plaintext_api_token, self.project.organization,
project_slug)
self.source_app_id = source_app_id
self.target_app_id = target_app_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def client(self):
transifex_project = TransifexProject.objects.get(slug=self.project_slug)
transifex_organization = transifex_project.organization
return TransifexApiClient(
transifex_organization.get_api_token,
transifex_organization.plaintext_api_token,
transifex_organization.slug,
self.project_slug,
self.use_version_postfix,
Expand Down
20 changes: 16 additions & 4 deletions corehq/apps/translations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
from django.contrib import admin
from django.db import models
from django.urls import reverse
from django.utils.functional import cached_property

from corehq.apps.app_manager.dbaccessors import get_app, get_app_ids_in_domain
from corehq.motech.utils import b64_aes_decrypt
from corehq.motech.const import ALGO_AES_CBC
from corehq.motech.utils import (
b64_aes_decrypt,
b64_aes_cbc_decrypt,
b64_aes_cbc_encrypt,
)
from corehq.util.quickcache import quickcache


Expand Down Expand Up @@ -165,10 +169,18 @@ class TransifexOrganization(models.Model):
def __str__(self):
return self.name + ' (' + self.slug + ')'

@cached_property
def get_api_token(self):
@property
def plaintext_api_token(self):
if self.api_token.startswith(f'${ALGO_AES_CBC}$'):
ciphertext = self.api_token.split('$', 2)[2]
return b64_aes_cbc_decrypt(ciphertext)
return b64_aes_decrypt(self.api_token)

@plaintext_api_token.setter
def plaintext_api_token(self, plaintext):
jingcheng16 marked this conversation as resolved.
Show resolved Hide resolved
ciphertext = b64_aes_cbc_encrypt(plaintext)
self.api_token = f'${ALGO_AES_CBC}${ciphertext}'


class TransifexProject(models.Model):
organization = models.ForeignKey(TransifexOrganization, on_delete=models.CASCADE)
Expand Down
9 changes: 9 additions & 0 deletions corehq/motech/openmrs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
IMPORT_FREQUENCY_DAILY,
IMPORT_FREQUENCY_MONTHLY,
IMPORT_FREQUENCY_WEEKLY,
ALGO_AES_CBC,
)
from corehq.motech.utils import b64_aes_decrypt, b64_aes_cbc_decrypt
from corehq.motech.openmrs.const import (
OPENMRS_DATA_TYPE_MILLISECONDS,
OPENMRS_DATA_TYPES,
Expand Down Expand Up @@ -130,6 +132,13 @@ def __str__(self):
def notify_addresses(self):
return [addr for addr in re.split('[, ]+', self.notify_addresses_str) if addr]

@property
def plaintext_password(self):
if self.password.startswith(f'${ALGO_AES_CBC}$'):
ciphertext = self.password.split('$', 2)[2]
return b64_aes_cbc_decrypt(ciphertext)
return b64_aes_decrypt(self.password)

@memoized
def get_timezone(self):
if self.timezone:
Expand Down
3 changes: 1 addition & 2 deletions corehq/motech/openmrs/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
from corehq.motech.openmrs.models import OpenmrsImporter, deserialize
from corehq.motech.openmrs.repeaters import OpenmrsRepeater
from corehq.motech.requests import get_basic_requests
from corehq.motech.utils import b64_aes_decrypt
from corehq.toggles.shortcuts import find_domains_with_toggle_enabled

RowAndCase = namedtuple('RowAndCase', ['row', 'case'])
Expand Down Expand Up @@ -255,7 +254,7 @@ def import_patients_to_domain(domain_name, force=False):
@task(queue='background_queue')
def import_patients_with_importer(importer_json):
importer = OpenmrsImporter.wrap(importer_json)
password = b64_aes_decrypt(importer.password)
password = importer.plaintext_password
requests = get_basic_requests(
importer.domain, importer.server_url, importer.username, password,
notify_addresses=importer.notify_addresses,
Expand Down
2 changes: 1 addition & 1 deletion corehq/motech/openmrs/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ def setUp(self):
self.send_mail_mock = self.send_mail_patcher.start()
self.import_patcher = patch('corehq.motech.openmrs.tasks.import_patients_of_owner')
self.import_mock = self.import_patcher.start()
self.decrypt_patcher = patch('corehq.motech.openmrs.tasks.b64_aes_decrypt')
self.decrypt_patcher = patch('corehq.motech.openmrs.models.b64_aes_decrypt')
self.decrypt_patcher.start()

def tearDown(self):
Expand Down
6 changes: 3 additions & 3 deletions corehq/motech/openmrs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from corehq.apps.hqwebapp.decorators import use_bootstrap5
from corehq.apps.users.decorators import require_permission
from corehq.apps.users.models import HqPermissions
from corehq.motech.const import PASSWORD_PLACEHOLDER
from corehq.motech.const import PASSWORD_PLACEHOLDER, ALGO_AES_CBC
from corehq.motech.openmrs.dbaccessors import get_openmrs_importers_by_domain
from corehq.motech.openmrs.forms import (
OpenmrsConfigForm,
Expand All @@ -38,7 +38,7 @@
from corehq.motech.openmrs.tasks import import_patients_to_domain
from corehq.motech.repeaters.models import RepeatRecord
from corehq.motech.repeaters.views import AddCaseRepeaterView, EditRepeaterView
from corehq.motech.utils import b64_aes_encrypt
from corehq.motech.utils import b64_aes_cbc_encrypt


@use_bootstrap5
Expand Down Expand Up @@ -155,7 +155,7 @@ def _update_importer(self, importer, data):
if value == PASSWORD_PLACEHOLDER:
continue # Skip updating the password if it hasn't been changed.
else:
value = b64_aes_encrypt(value)
value = f'${ALGO_AES_CBC}${b64_aes_cbc_encrypt(value)}'
elif key == 'report_params':
value = json.loads(value)
elif key == 'column_map':
Expand Down
Loading