Add mTLS option for redfish test

When the BMC only enables mTLS auth type at build time. Robot test
should be test normally. Adding "MTLS_ENABLED" environment variable as
an option and modifications to achieve it.

Signed-off-by: Tony Lee <tony.lee@quantatw.com>
Change-Id: I68c753a453f0c958c900f8ba3e1d73145e093d6c
diff --git a/docs/redfish_request_via_mTLS.md b/docs/redfish_request_via_mTLS.md
new file mode 100644
index 0000000..daeb7f8
--- /dev/null
+++ b/docs/redfish_request_via_mTLS.md
@@ -0,0 +1,81 @@
+Redfish Request Via mTLS
+=========================
+
+When the BMC only enables mTLS type for authentication. Redfish request in robot
+test should be tested normally.
+
+## Required environment variables in Robot
+
+  -  **MTLS_ENABLED** indicates whether mTLS is enabled in BMC.
+       False by default:
+
+        ```
+            ${MTLS_ENABLED}        False
+        ```
+
+  -  **VALID_CERT** indicates valid mTLS certificate for authentication.
+       When a redfish request doesn't specify a certificate, no certificate by
+       default.
+
+        ```
+            ${VALID_CERT}        ${EMPTY}
+        ```
+
+  -  **CERT_DIR_PATH** indicates path of mTLS certificates directory:
+
+        ```
+            ${CERT_DIR_PATH}        ${EMPTY}
+        ```
+## How to send a redfish request with certificate
+
+- When a redfish request is executed, it will be executed through the python
+   library **requests** with certificate. It supports for all Redfish REST
+   requests (get, head, post, put, patch, delete):
+
+   ```
+        import requests
+
+        cert_dict = kwargs.pop('certificate', {"certificate_name":VALID_CERT})
+        response = requests.get(
+                    url='https://'+ host + args[0],
+                    cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+                    verify=False,
+                    headers={"Cache-Control": "no-cache"})
+   ```
+
+- Original robot code of redfish request doesn’t need to modify. It will send
+   the request with the default certificate ${VALID_CERT}.
+
+- The example provides Redfish request to use other certificate in the Robot
+  code below:
+
+    ```
+    ${certificate_dict}=  Create Dictionary  certificate_name=${CERT}
+    Redfish.Get  ${VALID_URL}  certificate=&{certificate_dict}
+    ...  valid_status_codes=[${HTTP_OK}]
+    ```
+
+## Test Cases for mTLS authentication
+
+mTLS authentication is only a means to connect to the BMC, not for testing
+purposes. Therefore, some test cases need to write a new one to match it for
+mTLS authentication. (Requires test certificate with different privileges or
+username) Some cases don’t need to be tested because the purpose of
+them are inapplicable to mTLS. Case studies are as follows:
+
+- **Create_IPMI_User_And_Verify_Login_Via_Redfish**
+
+    In this case, it uses IPMI to create a random user with password and
+    privilege, and then verifies the login via Redfish. Therefore, it will
+    logout the default user and then login with the user just created by IPMI.
+    So it does not need to use mTLS to authenticate login and logout.
+    It can be replaced as follows: Prepare a certificate with the user name
+    "admin_user" in advance. Use IPMI to create a user named admin_user. Then
+    you can use the Redfish request with the admin_user certificate to provide
+    the server for verification.
+
+- **Attempt_Login_With_Expired_Session**
+
+    Most cases related to sessions don't require mTLS because Redfish requests
+    don't need to create a session first. Therefore, there is no need to test
+    these cases when mTLS is enabled.
diff --git a/lib/bmc_redfish.py b/lib/bmc_redfish.py
index 61b8177..036c990 100644
--- a/lib/bmc_redfish.py
+++ b/lib/bmc_redfish.py
@@ -14,6 +14,9 @@
 import gen_print as gp
 
 
+MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
+
+
 class bmc_redfish(redfish_plus):
     r"""
     bmc_redfish is a child class of redfish_plus that is designed to provide
@@ -39,8 +42,11 @@
         """
         self.__inited__ = False
         try:
-            super(bmc_redfish, self).__init__(*args, **kwargs)
-            self.__inited__ = True
+            if MTLS_ENABLED == 'True':
+                self.__inited__ = True
+            else:
+                super(bmc_redfish, self).__init__(*args, **kwargs)
+                self.__inited__ = True
         except ValueError as get_exception:
             except_type, except_value, except_traceback = sys.exc_info()
             regex = r"The HTTP status code was not valid:[\r\n]+status:[ ]+502"
@@ -62,6 +68,8 @@
         kwargs                      See parent class method prolog for details.
         """
 
+        if MTLS_ENABLED == 'True':
+            return None
         if not self.__inited__:
             message = "bmc_redfish.__init__() was never successfully run.  It "
             message += "is likely that the target BMC firmware code level "
@@ -77,6 +85,13 @@
         super(bmc_redfish, self).login(username, password, auth,
                                        *args, **kwargs)
 
+    def logout(self):
+
+        if MTLS_ENABLED == 'True':
+            return None
+        else:
+            super(bmc_redfish, self).logout()
+
     def get_properties(self, *args, **kwargs):
         r"""
         Return dictionary of attributes for a given path.
diff --git a/lib/bmc_redfish_utils.py b/lib/bmc_redfish_utils.py
index a39c5d9..ea39930 100644
--- a/lib/bmc_redfish_utils.py
+++ b/lib/bmc_redfish_utils.py
@@ -9,6 +9,8 @@
 from robot.libraries.BuiltIn import BuiltIn
 import gen_print as gp
 
+MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
+
 
 class bmc_redfish_utils(object):
 
@@ -22,15 +24,18 @@
         self.__inited__ = False
         self._redfish_ = BuiltIn().get_library_instance('redfish')
 
-        # There is a possibility that a given driver support both redfish and
-        # legacy REST.
-        self._redfish_.login()
-        self._rest_response_ = \
-            self._redfish_.get("/xyz/openbmc_project/", valid_status_codes=[200, 404])
-
-        # If REST URL /xyz/openbmc_project/ is supported.
-        if self._rest_response_.status == 200:
+        if MTLS_ENABLED == 'True':
             self.__inited__ = True
+        else:
+            # There is a possibility that a given driver support both redfish and
+            # legacy REST.
+            self._redfish_.login()
+            self._rest_response_ = \
+                self._redfish_.get("/xyz/openbmc_project/", valid_status_codes=[200, 404])
+
+            # If REST URL /xyz/openbmc_project/ is supported.
+            if self._rest_response_.status == 200:
+                self.__inited__ = True
 
         BuiltIn().set_global_variable("${REDFISH_REST_SUPPORTED}", self.__inited__)
 
diff --git a/lib/redfish_plus.py b/lib/redfish_plus.py
index 2257fc0..8364b01 100755
--- a/lib/redfish_plus.py
+++ b/lib/redfish_plus.py
@@ -7,6 +7,15 @@
 from redfish.rest.v1 import HttpClient
 import gen_print as gp
 import func_args as fa
+import requests
+import json
+from robot.libraries.BuiltIn import BuiltIn
+
+
+host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
+MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
+CERT_DIR_PATH = BuiltIn().get_variable_value("${CERT_DIR_PATH}")
+VALID_CERT = BuiltIn().get_variable_value("${VALID_CERT}")
 
 
 def valid_http_status_code(status, valid_status_codes):
@@ -110,28 +119,134 @@
 
     # Define rest function wrappers.
     def get(self, *args, **kwargs):
-        return self.rest_request(super(redfish_plus, self).get, *args,
-                                 **kwargs)
+
+        if MTLS_ENABLED == 'True':
+            return self.rest_request(self.get_with_mtls, *args, **kwargs)
+        else:
+            return self.rest_request(super(redfish_plus, self).get, *args,
+                                     **kwargs)
 
     def head(self, *args, **kwargs):
-        return self.rest_request(super(redfish_plus, self).head, *args,
-                                 **kwargs)
+
+        if MTLS_ENABLED == 'True':
+            return self.rest_request(self.head_with_mtls, *args, **kwargs)
+        else:
+            return self.rest_request(super(redfish_plus, self).head, *args,
+                                     **kwargs)
 
     def post(self, *args, **kwargs):
-        return self.rest_request(super(redfish_plus, self).post, *args,
-                                 **kwargs)
+
+        if MTLS_ENABLED == 'True':
+            return self.rest_request(self.post_with_mtls, *args, **kwargs)
+        else:
+            return self.rest_request(super(redfish_plus, self).post, *args,
+                                     **kwargs)
 
     def put(self, *args, **kwargs):
-        return self.rest_request(super(redfish_plus, self).put, *args,
-                                 **kwargs)
+
+        if MTLS_ENABLED == 'True':
+            return self.rest_request(self.put_with_mtls, *args, **kwargs)
+        else:
+            return self.rest_request(super(redfish_plus, self).put, *args,
+                                     **kwargs)
 
     def patch(self, *args, **kwargs):
-        return self.rest_request(super(redfish_plus, self).patch, *args,
-                                 **kwargs)
+
+        if MTLS_ENABLED == 'True':
+            return self.rest_request(self.patch_with_mtls, *args, **kwargs)
+        else:
+            return self.rest_request(super(redfish_plus, self).patch, *args,
+                                     **kwargs)
 
     def delete(self, *args, **kwargs):
-        return self.rest_request(super(redfish_plus, self).delete, *args,
-                                 **kwargs)
+
+        if MTLS_ENABLED == 'True':
+            return self.rest_request(self.delete_with_mtls, *args, **kwargs)
+        else:
+            return self.rest_request(super(redfish_plus, self).delete, *args,
+                                     **kwargs)
 
     def __del__(self):
         del self
+
+    def get_with_mtls(self, *args, **kwargs):
+
+        cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+        response = requests.get(url='https://' + host + args[0],
+                                cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+                                verify=False,
+                                headers={"Cache-Control": "no-cache"})
+
+        response.status = response.status_code
+        if response.status == 200:
+            response.dict = json.loads(response.text)
+
+        return response
+
+    def post_with_mtls(self, *args, **kwargs):
+
+        cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+        body = kwargs.pop('body', {})
+        response = requests.post(url='https://' + host + args[0],
+                                 json=body,
+                                 cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+                                 verify=False,
+                                 headers={"Content-Type": "application/json"})
+
+        response.status = response.status_code
+
+        return response
+
+    def patch_with_mtls(self, *args, **kwargs):
+
+        cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+        body = kwargs.pop('body', {})
+        response = requests.patch(url='https://' + host + args[0],
+                                  json=body,
+                                  cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+                                  verify=False,
+                                  headers={"Content-Type": "application/json"})
+
+        response.status = response.status_code
+
+        return response
+
+    def delete_with_mtls(self, *args, **kwargs):
+
+        cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+        response = requests.delete(url='https://' + host + args[0],
+                                   cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+                                   verify=False,
+                                   headers={"Content-Type": "application/json"})
+
+        response.status = response.status_code
+
+        return response
+
+    def put_with_mtls(self, *args, **kwargs):
+
+        cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+        body = kwargs.pop('body', {})
+        response = requests.put(url='https://' + host + args[0],
+                                json=body,
+                                cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+                                verify=False,
+                                headers={"Content-Type": "application/json"})
+
+        response.status = response.status_code
+
+        return response
+
+    def head_with_mtls(self, *args, **kwargs):
+
+        cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+        body = kwargs.pop('body', {})
+        response = requests.head(url='https://' + host + args[0],
+                                 json=body,
+                                 cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+                                 verify=False,
+                                 headers={"Content-Type": "application/json"})
+
+        response.status = response.status_code
+
+        return response
diff --git a/lib/resource.robot b/lib/resource.robot
index 9d5379e..91d81bb 100755
--- a/lib/resource.robot
+++ b/lib/resource.robot
@@ -21,6 +21,14 @@
 ${OPENBMC_PASSWORD}    0penBmc
 ${REST_USERNAME}       root
 ${REST_PASSWORD}       0penBmc
+
+# MTLS_ENABLED indicates whether mTLS is enabled.
+${MTLS_ENABLED}        False
+# Valid mTLS certificate for authentication.
+${VALID_CERT}          ${EMPTY}
+# Path of mTLS certificates directory.
+${CERT_DIR_PATH}       ${EMPTY}
+
 ${IPMI_PASSWORD}       0penBmc
 ${MACHINE_TYPE}    palmetto
 ${DBUS_POLL_INTERVAL}      15s