Repository: oppia/oppia

Issue:
`generate_signature` needs to work in both Python 2 and 3
Currently, there is no clear definition of whether the `classifier_services.generate_signature` should work on bytes or on str, in some places, it is called with the arg `message` of bytes type, in others it is called with the arg `message` of str type. Could we please unify this and make sure that `generate_signature` works properly in Python 3 too?


Discussion:

Resolving patch:
diff --git a/core/domain/classifier_services.py b/core/domain/classifier_services.py
--- a/core/domain/classifier_services.py
+++ b/core/domain/classifier_services.py
@@ -41,14 +41,15 @@ def generate_signature(secret, message, vm_id):
     """Generates digital signature for given data.
 
     Args:
-        secret: str. The secret used to communicate with Oppia-ml.
-        message: str. The message payload data.
+        secret: bytes. The secret used to communicate with Oppia-ml.
+        message: bytes. The message payload data.
         vm_id: str. The ID of the VM that generated the message.
 
     Returns:
         str. The signature of the payload data.
     """
-    message = '%s|%s' % (base64.b64encode(message), vm_id)
+    encoded_vm_id = python_utils.convert_to_bytes(vm_id)
+    message = b'%s|%s' % (base64.b64encode(message), encoded_vm_id)
     return hmac.new(
         secret, msg=message, digestmod=hashlib.sha256).hexdigest()
 
@@ -72,7 +73,8 @@ def verify_signature(oppia_ml_auth_info):
         return False
 
     generated_signature = generate_signature(
-        secret, oppia_ml_auth_info.message, oppia_ml_auth_info.vm_id)
+        secret, python_utils.convert_to_bytes(oppia_ml_auth_info.message),
+        oppia_ml_auth_info.vm_id)
     if generated_signature != oppia_ml_auth_info.signature:
         return False
     return True


Test patch:
diff --git a/core/controllers/acl_decorators_test.py b/core/controllers/acl_decorators_test.py
--- a/core/controllers/acl_decorators_test.py
+++ b/core/controllers/acl_decorators_test.py
@@ -3487,7 +3487,8 @@ def test_unauthorized_vm_cannot_fetch_jobs(self):
         payload['message'] = json.dumps('malicious message')
         payload['signature'] = classifier_services.generate_signature(
             python_utils.convert_to_bytes(secret),
-            payload['message'], payload['vm_id'])
+            python_utils.convert_to_bytes(payload['message']),
+            payload['vm_id'])
 
         with self.swap(self, 'testapp', self.mock_testapp):
             self.post_json(
@@ -3501,7 +3502,8 @@ def test_default_vm_id_raises_exception_in_prod_mode(self):
         payload['message'] = json.dumps('malicious message')
         payload['signature'] = classifier_services.generate_signature(
             python_utils.convert_to_bytes(secret),
-            payload['message'], payload['vm_id'])
+            python_utils.convert_to_bytes(payload['message']),
+            payload['vm_id'])
         with self.swap(self, 'testapp', self.mock_testapp):
             with self.swap(constants, 'DEV_MODE', False):
                 self.post_json(
@@ -3514,7 +3516,7 @@ def test_that_invalid_signature_raises_exception(self):
         payload['message'] = json.dumps('malicious message')
         payload['signature'] = classifier_services.generate_signature(
             python_utils.convert_to_bytes(secret),
-            'message', payload['vm_id'])
+            python_utils.convert_to_bytes('message'), payload['vm_id'])
 
         with self.swap(self, 'testapp', self.mock_testapp):
             self.post_json(
@@ -3527,7 +3529,8 @@ def test_that_no_excpetion_is_raised_when_valid_vm_access(self):
         payload['message'] = json.dumps('message')
         payload['signature'] = classifier_services.generate_signature(
             python_utils.convert_to_bytes(secret),
-            payload['message'], payload['vm_id'])
+            python_utils.convert_to_bytes(payload['message']),
+            payload['vm_id'])
 
         with self.swap(self, 'testapp', self.mock_testapp):
             json_response = self.post_json('/ml/nextjobhandler', payload)
diff --git a/core/controllers/base_test.py b/core/controllers/base_test.py
--- a/core/controllers/base_test.py
+++ b/core/controllers/base_test.py
@@ -1378,7 +1378,8 @@ def test_that_incorrect_derived_class_raises_exception(self):
         payload['message'] = json.dumps('message')
         payload['signature'] = classifier_services.generate_signature(
             python_utils.convert_to_bytes(secret),
-            payload['message'], payload['vm_id'])
+            python_utils.convert_to_bytes(payload['message']),
+            payload['vm_id'])
 
         with self.swap(self, 'testapp', self.mock_testapp):
             self.post_json(
@@ -1391,7 +1392,8 @@ def test_that_correct_derived_class_does_not_raise_exception(self):
         payload['message'] = json.dumps('message')
         payload['signature'] = classifier_services.generate_signature(
             python_utils.convert_to_bytes(secret),
-            payload['message'], payload['vm_id'])
+            python_utils.convert_to_bytes(payload['message']),
+            payload['vm_id'])
         with self.swap(self, 'testapp', self.mock_testapp):
             self.post_json(
                 '/correctmock', payload, expected_status_int=200)
diff --git a/core/controllers/classifier_test.py b/core/controllers/classifier_test.py
--- a/core/controllers/classifier_test.py
+++ b/core/controllers/classifier_test.py
@@ -121,7 +121,8 @@ def setUp(self):
         self.secret = feconf.DEFAULT_VM_SHARED_SECRET
         self.payload_proto.signature = classifier_services.generate_signature(
             python_utils.convert_to_bytes(self.secret),
-            self.payload_proto.job_result.SerializeToString(),
+            python_utils.convert_to_bytes(
+                self.payload_proto.job_result.SerializeToString()),
             self.payload_proto.vm_id)
 
         self.payload_for_fetching_next_job_request = {
@@ -132,7 +133,8 @@ def setUp(self):
         self.payload_for_fetching_next_job_request['signature'] = (
             classifier_services.generate_signature(
                 python_utils.convert_to_bytes(self.secret),
-                self.payload_for_fetching_next_job_request['message'],
+                python_utils.convert_to_bytes(
+                    self.payload_for_fetching_next_job_request['message']),
                 self.payload_for_fetching_next_job_request['vm_id']))
 
     def test_trained_classifier_handler(self):
@@ -235,7 +237,8 @@ def test_error_on_invalid_classifier_data_in_message(self):
         self.payload_proto.job_result.ClearField('classifier_frozen_model')
         self.payload_proto.signature = classifier_services.generate_signature(
             python_utils.convert_to_bytes(self.secret),
-            self.payload_proto.job_result.SerializeToString(),
+            python_utils.convert_to_bytes(
+                self.payload_proto.job_result.SerializeToString()),
             self.payload_proto.vm_id)
         self.post_blob(
             '/ml/trainedclassifierhandler',
@@ -545,7 +548,8 @@ def setUp(self):
         self.payload['message'] = json.dumps({})
         self.payload['signature'] = classifier_services.generate_signature(
             python_utils.convert_to_bytes(secret),
-            self.payload['message'], self.payload['vm_id'])
+            python_utils.convert_to_bytes(self.payload['message']),
+            self.payload['vm_id'])
 
     def test_next_job_handler(self):
         json_response = self.post_json(