# Security

## API Authority

### Background  

Without API authority, all users will NOT be able to call any API. This is a serious security breach which means that any user with any technical background can do almost everything with API.  

To tackle the issue, we have implemented API authority mechanism for each API call. A system needs to check whether a user has the authority to call this API, ensuring only authorized users can call APIs. Otherwise, the data might be wrongly changed by attackers.  

### API Authority Configuration

By default, platform API authority is activated, and the system will perform authority check each time when API is called. 
The check will be at API gateway level which means there will not be any impact on existing internal API service call. 

Tenant administrator normally needs to configure API-related permissions for each menu or data permission item, then assign these permissions and roles to users so that API authority check can pass. If API authority is not configured, the system will throw an error exception. 

For detailed API authority configuration process, see [API authority section in URP document](https://docs.insuremo.com/iam/urp#5-api-authority).


#### API Authority Switch in Config Server  

* For public cloud, InsureMO Ops team will maintain the parameters and keep authority switch on in all environments except development environment.   
* For private cloud, users can go to the config server and check the parameters below in the platform/public node:
  * **tenant.api.permission.exclude**: The tenant does not need API authority function. The wildcard means for all tenants.
  * **requestUrlCheckUrl.level**:  
     *  WARNING: When authorized check fails, warning message will show; 
     *  ERROR: When authorized check fails, an error message will show.

![Authority API](./image/security-config/security_config_001.PNG)


## Sensitive Business Data Mask For API

### Background

For sensitive data such as bank account or customer ID information passed in the API body, usually clients will ask to mask all the data; otherwise, the information will be easily disclosed to a third party.  

From the system perspective, based on the types of end users, there can be different scenarios:

* If it's an ordinary user using our business UI for operation, mask is always required to protect user’s privacy.
* If it's an authorized user using our business UI for operation and he has authority to bypass mask, it's necessary to create a new API then put the API path in the mask exclusion URL and a special permission authority.
* If it's a user from another system application for integration, then it's suggested to use long token to call, then it will always be unmasked and let front-end application mask it.

Now from platform perspective, mask rule is all default as switched on and users can change relative configuration to alter the mask rules.


### MaskUrls for Exclusion

By default, all the fields that URLs expose through API gateway will be masked. 

If it's not PAT token and users need some API to have unmasked fields, they can add the URL to the data table **MaskUrls** to make the table more specific as excluded URL is not to be masked (in the configured URL, the fields will not be masked).

The URL configuration cannot contain the module name but support wildcard. For example: `/v1/load,/v1/quotation,v1/save`. The `/v1/load` means the **proposal**`/v1/load`, **endo**`/v1/load`, or other modules' load functions with path `/v1/load` will not be masked.

![MaskUrls](./image/security-config/security_config_datatable_002.PNG)

### DomainModelMaskConfig for Model/Field

It defines which model and field will be masked in configured URLs.

![DomainModelMaskConfig](./image/security-config/security_config_datatable_003.PNG)


### MaskRuleDefinition for Rule

It controls the rule to be added if the default rules below do not meet business requirements. In special cases, users can even override data table to disable some rules that are not applicable to your market situation.

    * BANK_CARD: Bank Card. 
      * Only the first 6 numbers and last 4 numbers show, with the middle words masked as ****, for example: 622202****4123.
    * TELEPHONE: Telephone No. 
      * Only the first 2 words and last 1 word show, with the middle words masked as ****, for example: 10****3.
    * EMAIL: Email.
      * Only the first 2 words and last 1 word show, with the middle words masked as ****, for example: ye****y@163.com. 
    * ID_CARD: ID Card.
      * The first 6 numbers and last 4 numbers show, with the middle numbers masked as ****, for example: 421002****0012.
    * MOBILE: Mobile No. 
      * Only the first 3 numbers and last 4 numbers show, with the middle numbers masked as ****, for example: 138****4213. 
    * NAME: Name. 
      * 3 words mask，for example abc as a*
      * 2 words mask，for example ab as *b
      * more than 3 words mask, for example abcd as a**d

![MaskRuleDefinition](./image/security-config/security_config_datatable_004.PNG)

<div class="docs-tip"><span class="docs-admonitions-text">tip</span>

  * If both MaskStart and MaskEnd are set to be 0, then the whole field will be masked.
  * If the field value contains 10 words, but you set the MaskStart to be 11 and MaskEnd to be 12, then no word will be masked.

</div>


### Disable Mask in PAT Token

If the API is for integration purposes, sometimes users might complain that the Load API payload contains certain fields that are masked, making it difficult for the integrating customers to read and process data.

If that happens, please use long token (the personal access token) instead of OAuth token and it will not be masked in the payload.


### Mask Switch in Config Server

There are two scenarios: 

1. For public cloud, InsureMO team will maintain the parameters and keep authority switch on in all environments except some special cases. If you really want to switch authority off, please contact InsureMO support team with your business scenario.
2. For private cloud, users can go to the config server and check parameters below in the platform/public node.

The **tenant.domain.mask.exclude** parameter is used to control whether there's any tenant who does not use mask. The wildcard means not to use this feature for all tenants. Any change to this parameter requires assistance from Tech Support to restart the API gateway and all dependent services after the configuration is updated.


### Sample of Masked URL Response

Sample response of URL: `{{server}}/proposal/v1/load?policyId={{policyId}}` same as:

```json
{
  "AdjustedPremium": 5.18,
  "AnnualPremium": 630,
  "AutoUwResultCode": "1",
  "BeforeVatPremium": 5.18,
  "BusinessCateCode": "1",
  "BusinessObjectId": 3517051304219,
  "Commission": 0.52,
  "CommissionRate": 0.1,
  "DuePremium": 5.54,
  "EffectiveDate": "2019-04-22",
  "ExpiryDate": "2019-04-24",
  "GrossPremium": 5.18,
  "IsPremiumCalcSuccess": "Y",
  "IsRenewable": "Y",
  "IsTakeUpPolicy": "N",
  "POIRate": 0.0082191781,
  "PolicyCustomerList": [
    {
      "BusinessObjectId": 1000000290,
      "CustomerName": "C******r",
      "DateOfBirth": "1988-10-01",
      "IdNo": "***",
      "IdType": "1",
      "IsInsured": "N",
      "IsOrgParty": "N",
      "IsPolicyHolder": "Y",
      "PolicyElementId": 23855452830,
      "PolicyId": "23855452829,142D65ECBEF1892DA32FB0AB47E8570F",
      "PolicyStatus": 2,
      "SequenceNumber": 1,
      "TempData": {
        "CustomerName": "iiIm0wBdhTGVfmv35j7DaA==",
        "IdNo": "Jmf1vghdrScrTdpdeyKu7w=="
      }
    }
  ],
  "PolicyElementId": 23855452829,
  "PolicyId": "23855452829,142D65ECBEF1892DA32FB0AB47E8570F",
  "PolicyLobList": [
    {
      "AdjustedPremium": 5.18,
      "BeforeVatPremium": 5.18,
      "BusinessObjectId": 351861068,
      "GrossPremium": 5.18,
      "PersonList": [
        {
          "BusinessObjectId": 372580001,
          "CustomerName": "C******r",
          "DateOfBirth": "1988-10-01",
          "IdNo": "***",
          "IdType": "1",
          "IsPolicyHolder": "Y",
          "PolicyElementId": 23855452832,
          "PolicyId": "23855452829,142D65ECBEF1892DA32FB0AB47E8570F",
          "PolicyStatus": 2,
          "SequenceNumber": 1,
          "TempData": {
            "CustomerName": "iiIm0wBdhTGVfmv35j7DaA==",
            "IdNo": "Jmf1vghdrScrTdpdeyKu7w=="
          }
        }
      ],
      "PolicyElementId": 23855452831,
      "PolicyId": "23855452829,142D65ECBEF1892DA32FB0AB47E8570F",
      "PolicyRiskList": [
        {
          "AdjustedPremium": 5.18,
          "Age": 30,
          "BeforeVatPremium": 5.18,
          "BusinessObjectId": 351861075,
          "CustomerName": "C******r",
          "DateOfBirth": "1988-10-01",
          "GrossPremium": 5.18,
          "IdNo": "***",
          "IdType": "1",
          "PolicyCoverageList": [
            {
              "AdjustedPremium": 4.93,
              "AnnualPremium": 600,
              "ArrivalDate": "2020-09-17",
              "BeforeVatPremium": 4.93,
              "BusinessObjectId": 351861078,
              "GrossPremium": 4.93,
              "IsFinalLevelCt": "Y",
              "POIRate": 0.0082191781,
              "PolicyElementId": 23855452834,
              "PolicyId": "23855452829,142D65ECBEF1892DA32FB0AB47E8570F",
              "PolicyStatus": 2,
              "ProductElementCode": "C100416",
              "ProductElementId": 351926024,
              "SequenceNumber": 1,
              "SumInsured": 300000
            }
          ],
          "PolicyElementId": 23855452833,
          "PolicyId": "23855452829,142D65ECBEF1892DA32FB0AB47E8570F",
          "PolicyStatus": 2,
          "ProductElementCode": "R10007",
          "ProductElementId": 351926021,
          "RiskName": "InsuredName",
          "SequenceNumber": 1,
          "SumInsured": 900000,
          "TempData": {
            "CustomerName": "iiIm0wBdhTGVfmv35j7DaA==",
            "IdNo": "Jmf1vghdrScrTdpdeyKu7w=="
          }
        }
      ],
      "PolicyStatus": 2,
      "ProductCode": "TBTI",
      "ProductElementCode": "TBTI",
      "ProductElementId": 351925023,
      "ProductId": 351925022,
      "ProductLobId": 351925023,
      "SequenceNumber": 1,
      "SumInsured": 900000,
      "TechProductCode": "TR_POC",
      "TechProductId": 3516410623456,
      "TotalInsuredCount": 1
    }
  ],
  "PolicyStatus": 1,
  "PolicyType": "1",
  "PremiumCurrencyCode": "USD",
  "ProductCode": "TBTI",
  "ProductId": 351925022,
  "ProductVersion": "1.0",
  "ProposalDate": "2019-04-22",
  "ProposalNo": "PTBTI0000001151",
  "ProposalRejectDesc": "balabala reject description",
  "ProposalRejectReasonCode": "02",
  "ProposalStatus": "4",
  "SequenceNumber": 1,
  "SumInsured": 900000,
  "TechProductCode": "TR_POC",
  "TechProductId": 3516410623456,
  "Vat": 0.36,
  "VatRate": 0.07,
  "VersionSeq": 2
}

```

## Sensitive System ID Data Signature for API

### Background

System ID is often very regular and it means someone can repetitively try to use ID to load information which should not be visible to his/her authority.  

With our ID signature feature, users then will not be able to use any of system ID to conduct operation. He/She can only see and use the signature ID which is bound to his/hers account to load information. In order to get signature ID, users must conduct query first and the query function should have authority imposed.  

The whole operation step can be:

1. Users need call query API first to get the policy ID with their unique signature — query API must be BFF and with authority like branch authority imposed. Thus, if the user doesn't belong to appropriate branch, he/she will not be able to query and get the signature ID.

2. Use the signature ID to load information — application should control to expose only loadbyID API not loadbyPolicyNumber API.


### Data Signature Configuration

This function will be applied to all API URL requests that go through the API gateway. By default, any field that matches the name in below list will need to be signed in the request and response body.

`policyId,mainPolicyId,riPolicyId,relationPolicyId,appPolicyId,cedingPolicyId,appId,appDataId,lastPolicyId,dmsDocId,dmsEntityId,WorkflowBusinessKey,EndoId,masterPolicyId,CertificatePolicyId,SourceEndoId,relaBusiEntityId,letterId,referId,PaPolicyId,ClaimId,CaseId,ClientId,ServerId,FileId,refTransId,ClaimCaseId,ErrorListDmsDocId,EcsPaPolicyId,changeId,channelId,arapId,entity_id,collectionId,paymentId,offsetId,PartyId,printTaskId,refId,orgId,ParentOrgId`

Tenants can override the [Global Parameter](https://docs.insuremo.com/itables/app_framework/global_parameter) in platform table - SystemConfigTable so as to achieve its own rules.

* tenant.entityIdSign.urls.exclude--exclude URL for signature especially for those integration URL
* tenant.entityIdSign.fields--add new field to be signed apart from existing platform field


### Data Signature API

In some cases, users need to manually make ID signature when call our API.

For REST API call, we provide an API--`{{server}}/platform-pub/pubSecurity/signEntity?userName={{userName}}`.

For SDK call, for example

```
import com.insuremo.sdk.services.pub.PubSdkClient;

List<Map<String, Object>> PubSdkClient_SignEntity(){
  
List<Map<String, Object> requestBody = new ArrayList<>();

String userName = "";// required

PubSdkClient pubSdkClient = (PubSdkClient) getSDK("com.insuremo.sdk.services.pub.PubSdkClient");

List<Map<String, Object>> response = pubSdkClient.dataSignatureApi().newSignEntityRequestBuilder().requestBody(requestBody).userName(userName).doRequest().getBody();

return response;
}

```
            
        
Sample Request:

```
[
    {
        "Key": "PolicyId",
        "Value": 56878990923
    },
    {
        "Key": "DMSEntityId",
        "Value": 568789909232
    },
    {
        "Key": "EntityId",
        "Value": 568789909234
    }
]
```

In sample response, PolicyID and DMSEntityID is signed but the EntityId has not signed because it is not in the platform default signature field configuration.

```
[
    {
        "Key": "PolicyId",
        "Value": "56878990923,E44A56D902C5B16C2B8EB5CA07EA057A"
    },
    {
        "Key": "DMSEntityId",
        "Value": "568789909232,435FC67B4D1E9CECAD8BABCCBA15A67D"
    },
    {
        "Key": "EntityId",
        "Value": 568789909234
    }
]
```


### Disable Entity Signature in PAT Token

If the API is for integration purposes, sometimes users might complain that the Load API payload contains certain fields that have entity signature, making it difficult for the integrating customers to read and process data.

If that happens, please use long token (the personal access token) instead of OAuth token and it will not have entity signature in the payload.


### Data Signature Switch in Config Server

* For public cloud, InsureMO Ops team will maintain the parameters and keep authority switch on in all environments except development environment.   
* For private cloud, users can go to the config server and check parameters below in the platform/public node:
  * **tenant.entityIdSign.enable.exclude**: to enable or disable sign function(it is default as all, if any tenant no need, specify tenant code here).
  * **platform.foundation.entityIdSign.fields**: fields name need to be signed in API.
  * **platform.foundation.entityIdSign.urls.exclude**: fields will not be masked in the configured URL.

Please ask TS for help to restart the applications after the configuration is done. After restarting, the configuration will take effect.



## Sensitive API Request/Response Body in Log Encryption 

If there's any error for API, system will automatically capture request and response body for the API. It exposes a security threat as the API body might contain sensitive information like customer ID or mobile number.

![Log Encrypt](./image/monitor/log_encrypt.png)

Thus, it's controlled at platform layer that:

1. For development related environments, system will still display full API body.  
2. For UAT & production related environments, system will display full API body but in an encrypted way. 

If users want to see full API body, they can copy the body from log monitor and paste into log encryption section here. Then users will be able to see the full body.

![Log Decrypt](./image/monitor/log_decrypt.png)



## Sensitive Data Mask for System Log

### Background

The sensitive data such as bank account or customer ID information passed in log must be masked; otherwise, the information is easily disclosed to a third party. Although it's mentioned in the above chapter that system will automatically mask error log, it is still possible that developers mistakenly adding some logs which might expose the whole API request body.

Now mask rule is enabled by default and users can change relative configurations to activate mask rule.


### Mask Implementation for ICS

Our mask mechanism will use Regex expression to match all static texts produced in the tenant log to confirm whether they are produced from platform service or tenant BFF service.

Take Singapore ID card as an example. Since there's a regular pattern for Singapore ID, e.g., 1st digit with S/T, then 7 digits, then ending with an English character, we can 
extract it as a pattern then translate it into regex expression before configuring it into system.

Our platform has a set of default rules and tenants can override them.

Here's a platform sample of mask rules: — To mask email address and ID/mobile.

platform.logger.mask.rules=`[{"Disabled":"N","MaskRule":"(6,4)","Regex":"([1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx])|([1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2})","RuleName":"IdCardMaskRule"},{"Disabled":"N","MaskRule":"<(1,1)","Regex":"\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}","RuleName":"EmailMaskRule","Separator":"@"},{"Disabled":"N","MaskRule":"(3,4)","Regex":"0?(13|14|15|18)[0-9]{9}","RuleName":"MobileMaskRule"}]`

* Mandatory Input
1.	Regex -- Regex expression to match the text value in the log.
2.	MaskRule -- Range of the text to be masked from X at the beginning and from Y before the end.
3.	RuleName -- Name of Mask Rule.

* Not Mandatory Input:
1.	Separator -- Special text separator, e.g. "@" in an email example, to be used with "<" (text value to be masked before separator) and ">" (text value to be masked before separator).
2.	Disabled -- Indicate whether mask rule is switched on.

Here's a sample that a tenant overrides mask rules: -- To mask SG/HK ID/mobile and special token pattern

tenant.logger.mask.rules=`[{"Disabled":"N","MaskRule":"(2,2)","Regex":"[\"]+[STFG]\\d{7}[A-Z][\"]+|[\"]+[A-Z]{1,2}[0-9]{6}[0-9A][\"]","RuleName":"IdCardMaskRule"},{"Disabled":"N","MaskRule":"<(1,1)","Regex":"\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}","RuleName":"EmailMaskRule","Separator":"@"},{"Disabled":"N","MaskRule":"(2,2)","Regex":"[\"]+[89]\\d{7}[\"]+|[\"]+[69]\\d{7}[\"]","RuleName":"MobileMaskRule"},{"Disabled": "N","MaskRule": ">(2,1)","Regex": "(?i)(\")?(apiKey|TOKENCODE|access_token|secretKey|client_secret|utm-key|password)(\")?:[ ]?(\")?([^\"\",}\\]]+)(\"(?=\\,)|\"(?=\\})|\"(?=\\])|\"(?= )|\"|(?=\\,)|(?=\\})|(?=\\])|(?= ))","RuleName": "TokenMaskRuleColon","Separator": ":"},{"Disabled": "N","MaskRule": ">(2,1)","Regex": "(?i)(apiKey|TOKENCODE|access_token|secretKey|client_secret|utm-key|password) (\")?=[ ]?(\")?([^\"\",}\\]]+)(\"(?=\\,)|\"(?=\\})|\"(?=\\])|\"(?= )|\"|(?=\\,)|(?=\\})|(?=\\])|(?= ))","RuleName": "TokenMaskRuleEqual","Separator": "="}]`


#### Sample of Mask API Response


The following is one of the actual results produced in the log after mask: -- Pay Attention to the Field "IdNumber" and "Email".

```json

{
    "@pk": 10000104948859,
    "@type": "IndiCustomer-IndiCustomer",
    "BusinessObjectId": 373904577,
    "ContactInfoList": [
        {
            "@pk": 10000104948860,
            "@type": "ContactInfo-ContactInfo",
            "AddressLine1": "aa",
            "AddressLine2": "11",
            "AddressType": "2",
            "BusinessObjectId": 373921322,
            "City": "11",
            "ContactId": 10000104948860,
            "CustomerId": 10000104948859,
            "Default": true,
            "Email": "7********2@qq.com",
            "Mobile": "22",
            "PostalCode": "12345",
            "StateOrProvince": "3",
            "AddressComplement": "bb",
            "Suburb": "cc"
        }
    ],
    "CustomerId": 10000104948859,
    "CustomerNumber": "CI00001053",
    "CustomerType": "IndiCustomer",
    "DateOfBirth": "2021-07-27T08:00:00",
    "FullName": "1111",
    "Gender": "M",
    "IdNumber": "371524********162334",
    "IdType": "1",
    "IsPep": "Y",
    "MonthlyIncome": 111
}

```

#### How to Debug MaskRule for Local Development

It is recommended to debug by using the below Java code directly. Once debug is finished, users can contact InsureMO team with complete string to configure into the config-center.

Example code:

```
import ...platform.foundation.utils.json.JsonBaseUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TestMaskRex {
    public static void main(String args[]) {


        String maskRuleRegex = "\"(BankAccountCode|BankName|BankAccountNo|BankAccountName)+\"?:\"([A-Za-z0-9][-A-Za-z0-9]+\\\")";

        Map<String,Object> infoMap=new HashMap<>();
        infoMap.put("Address","wqwwerfewrtet");
        infoMap.put("BankAccountCode","11111111");
        infoMap.put("BankAccountName","aaaaaaaaaa");
        infoMap.put("BankName","bbbbbbbbb");
        infoMap.put( "ContactPersonTel","2222222");
        infoMap.put("ContactPhone","33333333");
        infoMap.put("BankAccountNo","55555555");
        infoMap.put("Mobile","66666666");
        infoMap.put("Email","88888888@qq.com");

        String bankName= JsonBaseUtils.toJSON(infoMap);

        System.out.println(bankName);

        Pattern pattern = Pattern.compile(maskRuleRegex);
        Set<String> set = new HashSet<>();
        Matcher matcher = pattern.matcher(bankName);
        while (matcher.find()) {
            set.add(matcher.group());
        }
        //The matching string will be printed out
        System.out.println(set);
    }
}

```


#### Mask Switch in Config Server

* For public cloud, InsureMO Ops team will maintain the parameters and keep authority switch on in all environments except development environment.   
* For private cloud, users can go to the config server and check parameters below in platform/public node:
* **tenant.logger.mask.rules**: Tenants to provide specific mask rule

This parameter is added into hot-start so it takes effect without needs to start any service.

Since regex expression is not so readable, please test it in your BFF locally by specifying the parameter and only hand it to InsureMO team once the testing is ok.


### Mask Implementation for iHub

iHub is using the similar mask methods as ICS, but as iHub integration service is owned and deployed by tenants themselves, thus the mask rules will be maintained by tenants' config center.

And for default, iHub integration service use following default mask rules, you can customize to replace this at config center for an integration service layer or at public for the tenant’s all iHub service layer:

Configcenter key: logger.mask.rules

Configcenter Value(following is default, you can customize as your owne):

```
[
    {
        "Disabled": "N",
        "MaskRule": "(6,4)",
        "Regex": "([1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx])|([1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{2})",
        "RuleName": "IdCardMaskRule"
    },
    {
        "Disabled": "N",
        "MaskRule": "<(1,1)",
        "Regex": "\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}",
        "RuleName": "EmailMaskRule",
        "Separator": "@"
    },
    {
        "Disabled": "N",
        "MaskRule": "(3,4)",
        "Regex": "0?(13|14|15|18)[0-9]{9}",
        "RuleName": "MobileMaskRule"
    },
    {
        "Disabled": "N",
        "MaskRule": ">(3,3)",
        "Regex": "(?i)\"[^\"]*(?:token|password|passport|authorization|mobile|phone)[^\"]*\"\\s*:\\s*(?:(?:\\[[^\\]]*\\])|(?:(?:\"[^\"]*\")|\\S+))",
        "RuleName": "MaskForKeywordsInKey",
        "Separator": ":"
    }
]
```



## Sensitive Business Data Storage Encryption

### Background

By default, all the database data are encrypted already and TLS1.2 is enforced for data transaction encryption.

The sensitive data, such as bank account or customer ID information (also called PII data field) stored in the database, must be encrypted. By this way, even if someone gets the DB authority, he/she will not be able to see and steal any sensitive data.   

There are two levels that need attention.

1. If the data is stored in the platform core DB, encryption is switched on by default and achieved by configuration.  
2. If the data is stored in tenant DB, tenant BFF must be enhanced to encrypt sensitive data by tenant developer.

### Impact Analysis

Once data encryption is on, there are multiple impacts:

- Users will not be able to conduct fuzzy search on encrypted fields.
- Users will not be able to extract report to search or see encrypted fields.
- Users will not be able to conduct data patch on encrypted fields.
- Encryption is irreversible which means as long as fields are encrypted, they will always be encrypted.
- There should be super careful management process to maintain the encryption key. If the key is lost, the entire database will risk damage.

So users must be very careful to select whether to switch on encryption and which fields to be encrypted.

### Encryption Configuration

Currently, all encryption configuration is defined at a platform side. By default, any field that matches the names below will be encrypted in the backend.

"AccountHolderIdNo,AccountNo,BankAccount,BankAccountCode,BankAccountNo,CcEmail,CcMobile,ContactEmail,CustomerAccountNo,CustomerIdNo,Email,IdNo,Mobile,IndiIdNo"


#### Encryption in Config Server

* For public cloud, InsureMO Ops team will maintain the parameters and keep authority switch on in all environments except development environment.   
* For private cloud, users can go to the config server and check the parameters below in the platform/public node:
  * **encrypt.sensitive.field.includeTenants**: The tenant need encryption function. The wildcard * means for all tenants no need encryption.
  * **encrypt.sensitive.field.name.list** Defines all field names should be encrypted. For example: `AccountHolderIdNo,AccountNo,BankAccount,BankAccountCode,BankAccountNo,CcEmail,CcMobile,ContactEmail,CustomerAccountNo,CustomerIdNo,Email,IdNo,Mobile,IndiIdNo`
  * **encrypt.aes.key**: AES encrypt/decrypt key, which must be 32 bytes. This field is no use for KMS.
  * **encrypt.aes.iv**: AES initial vector, which must be 16 bytes. This field is no use for KMS.  
  * **encrypt_v2.aes.key.kms.provider**: Determine who to provide KMS. usually it will be container.
  * **kms_key_alias**: Determine the alias for the key 

Please ask SiteOps for help to restart the applications after the configuration is done. After restarting, the configuration will take effect.


#### Sample of Encryption Response

For example, we encrypt Email, IdNo, Mobile. Then try pa-related APIs. For example: `{{server}}/proposal/v1/application`.

The request data is as follows. Although there's no change in API layer, data is encrypted in database table.

```json
{
  "ProductCode": "TBTI",
  "ProductVersion": "1.0",
  "ProposalDate": "2019-04-22T00:00:00",
  "EffectiveDate": "2019-04-22T00:00:00",
  "ExpiryDate": "2019-04-24T23:59:59",
  "PolicyCustomerList": [
    {
      "CustomerName": "Customer",
      "DateOfBirth": "1988-10-01",
      "IdType": "1",
      "IdNo": "66666666",
      "Mobile": "88888888",
      "Email": "xxx@xxx.com",
      "IsInsured": "N",
      "IsPolicyHolder": "Y"
    }
  ],
  "PolicyLobList": [
    {
      "PolicyRiskList": [
        {
          "CustomerName": "Customer",
          "IdNo": "66666666",
          "Mobile": "88888888",
          "Email": "xxx@xxx.com",
          "IdType": "1",
          "DateOfBirth": "1988-10-01",
          "PolicyCoverageList": [
            {
              "ProductElementCode": "C100416",
              "SumInsured": 300000
            },
            {
              "ProductElementCode": "C100692",
              "SumInsured": 300000
            },
            {
              "ProductElementCode": "C100715",
              "SumInsured": 300000
            }
          ],
          "ProductElementCode": "R10007",
          "RiskName": "InsuredName"
        }
      ],
      "ProductCode": "TBTI",
      "TotalInsuredCount": 1
    }
  ]
}
```

The Response:

```json
{
    "AdjustedPremium": 5.178082203,
    "AnnualPremium": 630,
    "AutoUwResultCode": "1",
    "BeforeVatPremium": 5.178082203,
    "BusinessCateCode": "1",
    "BusinessObjectId": 3517051304219,
    "Commission": 0.52,
    "CommissionRate": 0.1,
    "DuePremium": 5.54,
    "EffectiveDate": "2019-04-22",
    "ExpiryDate": "2019-04-24T23:59:59",
    "GrossPremium": 0,
    "IsPremiumCalcSuccess": "Y",
    "IsRenewable": "Y",
    "IsTakeUpPolicy": "N",
    "POIRate": 0.0082191781,
    "PolicyCustomerList": [
        {
            "BusinessObjectId": 1000000290,
            "CustomerName": "Customer",
            "DateOfBirth": "1988-10-01",
            "Email": "xxx@xxx.com",
            "IdNo": "66666666",
            "IdType": "1",
            "IsInsured": "N",
            "IsOrgParty": "N",
            "IsPolicyHolder": "Y",
            "Mobile": "88888888",
            "PolicyElementId": 856313834,
            "PolicyId": 856313833,
            "PolicyStatus": 2,
            "SequenceNumber": 1
        }
    ],
    "PolicyElementId": 856313833,
    "PolicyId": 856313833,
    "PolicyLobList": [
        {
            "AdjustedPremium": 5.178082203,
            "BeforeVatPremium": 5.178082203,
            "BusinessObjectId": 351861068,
            "GrossPremium": 0,
            "PolicyElementId": 856313835,
            "PolicyId": 856313833,
            "PolicyRiskList": [
                {
                    "AdjustedPremium": 5.178082203,
                    "Age": 30,
                    "BeforeVatPremium": 5.178082203,
                    "BusinessObjectId": 351861075,
                    "CustomerName": "Customer",
                    "DateOfBirth": "1988-10-01",
                    "Email": "xxx@xxx.com",
                    "GrossPremium": 0,
                    "IdNo": "66666666",
                    "IdType": "1",
                    "Mobile": "88888888",
                    "PolicyCoverageList": [
                        {
                            "AdjustedPremium": 4.93150686,
                            "AnnualPremium": 600,
                            "BeforeVatPremium": 4.93150686,
                            "BusinessObjectId": 351861078,
                            "GrossPremium": 0,
                            "IsFinalLevelCt": "Y",
                            "POIRate": 0.0082191781,
                            "PolicyElementId": 856313837,
                            "PolicyId": 856313833,
                            "PolicyStatus": 2,
                            "ProductElementCode": "C100416",
                            "ProductElementId": 351926024,
                            "SequenceNumber": 1,
                            "SumInsured": 300000
                        }
                    ],
                    "PolicyElementId": 856313836,
                    "PolicyId": 856313833,
                    "PolicyStatus": 2,
                    "ProductElementCode": "R10007",
                    "ProductElementId": 351926021,
                    "RiskName": "InsuredName",
                    "SequenceNumber": 1,
                    "SumInsured": 900000
                }
            ],
            "PolicyStatus": 2,
            "ProductCode": "TBTI",
            "ProductElementCode": "TBTI",
            "ProductElementId": 351925023,
            "ProductId": 351925022,
            "ProductLobId": 351925023,
            "SequenceNumber": 1,
            "SumInsured": 900000,
            "TechProductCode": "TR_POC",
            "TechProductId": 3516410623456,
            "TotalInsuredCount": 1
        }
    ],
    "PolicyStatus": 1,
    "PolicyType": "1",
    "PremiumCurrencyCode": "USD",
    "ProductCode": "TBTI",
    "ProductId": 351925022,
    "ProductVersion": "1.0",
    "ProposalDate": "2019-04-22",
    "ProposalNo": "PTBTI0000002862",
    "ProposalStatus": "2",
    "SequenceNumber": 1,
    "SumInsured": 900000,
    "TechProductCode": "TR_POC",
    "TechProductId": 3516410623456,
    "Vat": 0.36,
    "VatRate": 0.07,
    "VersionSeq": 1
}
```


Check the insured data records in DB:

```mysql
SELECT	t.`POLICY_ID`,t.`ELEMENT_TYPE`,t.`DYNAMIC_FIELDS`,t.* FROM t_pa_pl_policy_element t WHERE t.`POLICY_ID`='856313833';
```

Search by Policy ID in response, and the record shows the encryption as:

![Data Encrypted_Insured](./image/security-config/security_config_check_001.PNG)

The data record is shown as:

```json
{"Age":30,"CustomerName":"Customer","DateOfBirth":"1988-10-01","Email":"{AES}oWmtQ2ZEaw1S6bzo4187zNwdfBXXu38Mk0Jh9M73L68=","IdNo":"{AES}gANBmcCLSIkg4A4C2azW4FqEnvJmzLzMaynyZ0LoM4U=","IdType":"1","Mobile":"{AES}CE7V9kFpNcC1TWplzIufjg=="}
```

Check the PolicyCustomer:
```mysql
SELECT	t.`POLICY_ID`,t.`ID_NO`,t.`EMAIL`,t.`MOBILE`,t.* FROM t_pa_pl_customer t WHERE t.`POLICY_ID`='856313833';
```

![Data Encrypted_PolicyCustomer](./image/security-config/security_config_check_002.PNG)



## Issuance API Hash Calculate Check (General Insurance Only)

### Background

Currently, most issuance APIs accept the full policy body. It is possible that someone can call issuance API by passing any final premium value to be persisted without calculation. In an extreme case, it can be 0.01 even 0 amount of premium to be issued by passing any of system premium calculation. 

To avoid such a thing, a check is imposed if any due premium is changed while no calculation has been performed ever since, and the system will not allow policy to be issued unless a premium re-calculation is triggered. This is to ensure policy can only be issued with the latest calculated result.    

For those tenants who adopt GIMO issuance API but not calculation API, we need specifically exclude them from this validation.  


### Hash Calculate Check Switch in Config Table

* The table name is **BusinessConfigTable**. The Switch item is below,

  * **calculate.hash.validate.ignored**: to enable or disable hash calculate check.
    * Y, disable the hash calculate check.
    * N, enable the hash calculate check. the default is N,enable the hash calculate check.

  After updating, the configuration will take effect directly, there is no need to restart any service.

![ELK](./image/security-config/sign_calculate_sw.png) 


### Hash Calculation Check Configuration

By default, the platform has provided a set of embedded rules configured in Data Table. Tenants can override the data table to achieve its own rules.

* PaPremCalcResultColumnConfig: the related premiums to validate are defined.
* PaPremCalcFactorColumnConfig: the related rating factors to validate are defined.

### Hash Calculation Check Configuration Debug

1. Error Message: policy premium calculation result was modified by client side.
2. Error Message: once policy premium calculation factor is changed, it should be re-calculated before issue!

#### Scenario
   When issuing policy, sometimes error messages will pop up.
   * "Policy premium calculation result was modified by client side."  
   * "Policy premium calculation factor is changed and it should be re-calculated before issue."

#### Cause
   System is a tamper proof to avoid policy/premium being modified. When an error pops up, it means the premiums/or some important fields' values are different from the stored values when calculating/rating.
   In Product developing, for most of the time, it is caused by the rating Premiums not being rounded to 2 digitals or less than 2 digitals. Please check the rating formula, and fix it with proper rounding.

#### Debug Steps (from Version 22.10)
  From Version 22.10, we do not need to set the logger level now.

  1. Just get the trace ID, then search in ELK.

  ![ELK](./image/security-config/ELK_SelectFields.png) 


  2. Find the key word "**Data to sign**", then get the detailed message. The message details are similar the following.

*  The message showing `"config table=PaPremCalcFactorColumnConfig"` means the data is for rating factors to sign validation. 
*  The message showing `"config table=PaPremCalcResultColumnConfig"` means the data is for premiums to sign validation. 

If data is encoded, please decode it under **Public Setting > Decode Tool**.


![Data to sign](./image/security-config/ELK_data_to_sign_issuePolicy.png) 

If the rating factors check fails, the log shows as below:

   ```
   Data to sign for validate: (config table=PaPremCalcFactorColumnConfig): [
    {Installment-Installment,FeeSeq=1,InstallmentDate=2021-04-22T00: 00,InstallmentPeriodSeq=1,PolicyStatus=2,SumInsured=0
    },
    {Installment-Installment,FeeSeq=1,InstallmentDate=2022-03-22T00: 00,InstallmentPeriodSeq=13,PolicyStatus=2,SumInsured=0
    },
    {Policy-POLICY,AdjustedPremium=630,AdjustedPremiumLocal=0,AgentCode=PTY10000104732814,AgreementCode=,BookCurrencyCode=USD,BusinessCateCode=1,EffectiveDate=2021-04-22T00: 00,ExpiryDate=2022-04-21T23: 59: 59,IsShortTerm=,LocalCurrencyCode=USD,MasterPolicyNo=,OrgCode=10002,PlanCode=,PolicyStatus=1,PolicyType=1,PremiumBookExchangeRate=1,PremiumCurrencyCode=USD,PremiumLocalExchangeRate=1,ProductCode=TBTI,SiCurrencyCode=,SiLocalExchangeRate=0,SumInsured=900000,SumInsuredLocal=0,TechProductCode=TR_POC
    },
    {PolicyAgent-PolicyAgent,PolicyStatus=2
    },
    {PolicyBenefit-PolicyBenefit,EffectiveDate=,ExpiryDate=,PolicyStatus=2,ProductElementCode=B00248,SumInsured=0,SumInsuredLocal=0
    },
    {PolicyCoverage-PolicyCoverage,EffectiveDate=,ExpiryDate=,PolicyStatus=2,ProductElementCode=C100416,SumInsured=300000,SumInsuredLocal=0
    },
    {PolicyCoverage-PolicyCoverage,EffectiveDate=,ExpiryDate=,PolicyStatus=2,ProductElementCode=C100692,SumInsured=300000,SumInsuredLocal=0
    },
    {PolicyCoverage-PolicyCoverage,EffectiveDate=,ExpiryDate=,PolicyStatus=2,ProductElementCode=C100715,SumInsured=300000,SumInsuredLocal=0
    },
    {PolicyCustomer-PolicyCustomer,PolicyStatus=2
    },
    {PolicyLob-TR_POC,EffectiveDate=,ExpiryDate=,PolicyStatus=2,ProductElementCode=TBTI,SumInsured=900000,SumInsuredLocal=0
    },
    {PolicyPaymentInfo-PolicyPaymentInfo,InstallmentPeriodCount=12,IsInstallment=Y,PayRate=1,PolicyStatus=2
    },
    {PolicyRisk-R10007,EffectiveDate=,ExpiryDate=,NumberOfCopies=0,PlanCode=,PolicyStatus=2,ProductElementCode=R10007,RiskGroupNo=0,SumInsured=900000,SumInsuredLocal=0
    },
    20221116_113814_329
], hash: 20221116_113814_329,rdIGO4H0o6FJ/F8ma5vtmK8WoHHcz3s/l4Om+E/HdaI=  (PolicyId=2674600007)
   ```

If the premiums check fails, the log shows as below. 

 ```
 Data to sign for validate: (config table=PaPremCalcResultColumnConfig): [
    {Installment-Installment,BeforeVatPremium=52.5,BeforeVatPremiumLocal=52.5,Commission=0,CommissionLocal=0,DuePremium=52.5,FeeSeq=1,InstallmentAmount=52.5,InstallmentAmountLocal=52.5,InstallmentDate=2021-04-22T00: 00,InstallmentPeriodSeq=1,Vat=0,VatLocal=0
    },
    {Installment-Installment,BeforeVatPremium=52.5,BeforeVatPremiumLocal=52.5,Commission=0,CommissionLocal=0,DuePremium=52.5,FeeSeq=1,InstallmentAmount=52.5,InstallmentAmountLocal=52.5,InstallmentDate=2022-03-22T00: 00,InstallmentPeriodSeq=12,Vat=0,VatLocal=0
    },
    {Policy-POLICY,BeforeVatPremium=630,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=630,DuePremiumLocal=0,GrossPremium=630,GrossPremiumLocal=0,PremCalcFactorHash=20221116_113814_329,
        451TI40WL48vX0G9OKVFSoJjIm/51KQNV30XlURvRkg=,Vat=0.1,VatLocal=0
    },
    {PolicyAgent-PolicyAgent,Commission=0,CommissionLocal=0
    },
    {PolicyBenefit-PolicyBenefit,BeforeVatPremium=0,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=0,GrossPremiumLocal=0,Vat=0,VatLocal=0
    },
    {PolicyCoverage-PolicyCoverage,BeforeVatPremium=10,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=10,GrossPremiumLocal=0,Vat=0,VatLocal=0
    },
    {PolicyCoverage-PolicyCoverage,BeforeVatPremium=20,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=20,GrossPremiumLocal=0,Vat=0,VatLocal=0
    },
    {PolicyCoverage-PolicyCoverage,BeforeVatPremium=600,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=600,GrossPremiumLocal=0,Vat=0,VatLocal=0
    },
    {PolicyLob-TR_POC,BeforeVatPremium=630,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=630,GrossPremiumLocal=0,Vat=0,VatLocal=0
    },
    {PolicyPaymentInfo-PolicyPaymentInfo,BeforeVatPremium=0,Commission=0,DuePremium=0
    },
    {PolicyRisk-R10007,BeforeVatPremium=630,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=630,GrossPremiumLocal=0,PremCalcFactorHash=,Vat=0,VatLocal=0
    },
    20221116_113814_334
], hash: 20221116_113814_334,Q64a6NUM1MpNYdhXl7Z89A2yOMIZsqZ72FIredYkxCA=  (PolicyId=2674600007)

 ```

And there is a log shown as follows:

`
Sign field not match,expect:20230210_144823_905,cCOqWtQwIflPJ5GsGey0YurlkmpZ23GolTKWR7ecu3M=,real:20230210_144823_905,QZ3Arq+tiltNmuEibCO3AM/h9KXYxou4bEoX4UBcCFo=,config table=PaPremCalcFactorColumnConfig. To find the root cause of this error, please search in the application log using the timestamp of the two sign: "20230210_144823_905" & "20230210_144823_905" , and compare the data to sign of current transaction & previous.  (PolicyId=7922520060)
`


  3. Get "**PolicyId**" or **timestamp** in the message, and then search it in ELK. 
  4. To find the record for "request_path" is "**/v1/calculate**" or "**/v1/persist/calculate**" and key word "**Data to sign**", and then get the detailed message. The message details are similar to the following.  

    ![Data to Sign of calculate API](./image/security-config/ELK_data_to_sign_calculate.png) 


```
Data to sign (config table=PaPremCalcFactorColumnConfig): [
    {Installment-Installment,FeeSeq=1,InstallmentDate=2021-04-22T00: 00,InstallmentPeriodSeq=1,PolicyStatus=2,SumInsured=0
    },
    {Installment-Installment,FeeSeq=1,InstallmentDate=2022-03-22T00: 00,InstallmentPeriodSeq=12,PolicyStatus=2,SumInsured=0
    },
    {Policy-POLICY,AdjustedPremium=630,AdjustedPremiumLocal=0,AgentCode=PTY10000104732814,AgreementCode=,BookCurrencyCode=USD,BusinessCateCode=1,EffectiveDate=2021-04-22T00: 00,ExpiryDate=2022-04-21T23: 59: 59,IsShortTerm=,LocalCurrencyCode=USD,MasterPolicyNo=,OrgCode=10002,PlanCode=,PolicyStatus=1,PolicyType=1,PremiumBookExchangeRate=1,PremiumCurrencyCode=USD,PremiumLocalExchangeRate=1,ProductCode=TBTI,SiCurrencyCode=,SiLocalExchangeRate=0,SumInsured=900000,SumInsuredLocal=0,TechProductCode=TR_POC
    },
    {PolicyAgent-PolicyAgent,PolicyStatus=2
    },
    {PolicyBenefit-PolicyBenefit,EffectiveDate=,ExpiryDate=,PolicyStatus=2,ProductElementCode=B00248,SumInsured=0,SumInsuredLocal=0
    },
    {PolicyCoverage-PolicyCoverage,EffectiveDate=,ExpiryDate=,PolicyStatus=2,ProductElementCode=C100416,SumInsured=300000,SumInsuredLocal=0
    },
    {PolicyCoverage-PolicyCoverage,EffectiveDate=,ExpiryDate=,PolicyStatus=2,ProductElementCode=C100692,SumInsured=300000,SumInsuredLocal=0
    },
    {PolicyCoverage-PolicyCoverage,EffectiveDate=,ExpiryDate=,PolicyStatus=2,ProductElementCode=C100715,SumInsured=300000,SumInsuredLocal=0
    },
    {PolicyCustomer-PolicyCustomer,PolicyStatus=2
    },
    {PolicyLob-TR_POC,EffectiveDate=,ExpiryDate=,PolicyStatus=2,ProductElementCode=TBTI,SumInsured=900000,SumInsuredLocal=0
    },
    {PolicyPaymentInfo-PolicyPaymentInfo,InstallmentPeriodCount=12,IsInstallment=Y,PayRate=1,PolicyStatus=2
    },
    {PolicyRisk-R10007,EffectiveDate=,ExpiryDate=,NumberOfCopies=0,PlanCode=,PolicyStatus=2,ProductElementCode=R10007,RiskGroupNo=0,SumInsured=900000,SumInsuredLocal=0
    },
    20221116_113814_329
], hash: 20221116_113814_329,
451TI40WL48vX0G9OKVFSoJjIm/51KQNV30XlURvRkg=  (PolicyId=2674600007)

```

or

```
Data to sign (config table=PaPremCalcResultColumnConfig): [
    {Installment-Installment,BeforeVatPremium=52.5,BeforeVatPremiumLocal=52.5,Commission=0,CommissionLocal=0,DuePremium=52.5,FeeSeq=1,InstallmentAmount=52.5,InstallmentAmountLocal=52.5,InstallmentDate=2021-04-22T00: 00,InstallmentPeriodSeq=1,Vat=0,VatLocal=0
    },
    {Installment-Installment,BeforeVatPremium=52.5,BeforeVatPremiumLocal=52.5,Commission=0,CommissionLocal=0,DuePremium=52.5,FeeSeq=1,InstallmentAmount=52.5,InstallmentAmountLocal=52.5,InstallmentDate=2022-03-22T00: 00,InstallmentPeriodSeq=12,Vat=0,VatLocal=0
    },
    {Policy-POLICY,BeforeVatPremium=630,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=630,DuePremiumLocal=0,GrossPremium=630,GrossPremiumLocal=0,PremCalcFactorHash=20221116_113814_329,
        451TI40WL48vX0G9OKVFSoJjIm/51KQNV30XlURvRkg=,Vat=0,VatLocal=0
    },
    {PolicyAgent-PolicyAgent,Commission=0,CommissionLocal=0
    },
    {PolicyBenefit-PolicyBenefit,BeforeVatPremium=0,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=0,GrossPremiumLocal=0,Vat=0,VatLocal=0
    },
    {PolicyCoverage-PolicyCoverage,BeforeVatPremium=10,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=10,GrossPremiumLocal=0,Vat=0,VatLocal=0
    },
    {PolicyCoverage-PolicyCoverage,BeforeVatPremium=20,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=20,GrossPremiumLocal=0,Vat=0,VatLocal=0
    },
    {PolicyCoverage-PolicyCoverage,BeforeVatPremium=600,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=600,GrossPremiumLocal=0,Vat=0,VatLocal=0
    },
    {PolicyLob-TR_POC,BeforeVatPremium=630,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=630,GrossPremiumLocal=0,Vat=0,VatLocal=0
    },
    {PolicyPaymentInfo-PolicyPaymentInfo,BeforeVatPremium=0,Commission=0,DuePremium=0
    },
    {PolicyRisk-R10007,BeforeVatPremium=630,BeforeVatPremiumLocal=0,Commission=0,CommissionLocal=0,DuePremium=0,DuePremiumLocal=0,GrossPremium=630,GrossPremiumLocal=0,PremCalcFactorHash=,Vat=0,VatLocal=0
    },
    20221116_113814_334
], hash: 20221116_113814_334,ihhAWcrD0z9EjKtR+Uq9lGXiKuJ2jN7dwEcZS7O7dLI=  (PolicyId=2674600007)

```

  Then compare it with the message when issuing. You can get the different point now. Then just find the cause of the change and fix it.


#### Debugging Steps (before Version 22.10)

  1. Set the logger level of "**...platform.data.protection.impl.ModelSignServiceImpl**" under the application (take proposal for example) to be **DEBUG**. 
     Please check the log in ELK to find the application name of platform (BFF always calls the APIs of quotation or proposal), and then set the level.
     

From Version 22.10, we do not need to set the logger level of "...platform.data.protection.impl.ModelSignServiceImpl". Just see [Debug Steps (From 22.10)](#debug-steps-from-version-2210).

  ![set ModelSignServiceImpl to be DEBUG](./image/security-config/sign_debug.png)

  2. Call the API with premium calculation/ rating before issue, then get the trace ID A.
  3. Call the API of issue, then get the trace ID B.
  4. Search the trace ID in Kibana, and find the key word "**Data to sign**".

    When issuing, the system validates whether the rating factors and premium are modified or not. As is in the picture below, the red one is recorded for premium sign validation, and the blue one for rating factor sign validation. The rating factor-related field is defined in data tale "**PaPremCalcFactorColumnConfig**" and the premium-related fields are defined in data table "**PaPremCalcResultColumnConfig**".

   *  Rating factor modified: Policy premium calculation result was modified by client side.
   *  Premium modified: Once Policy premium calculation factor is changed, it should be re-calculated before issue.


   ![Data to Sign of calculate API](./image/security-config/sign_calculate_data.png) 

   ![Data to Sign of issue API](./image/security-config/sign_issue_data.png) 

  5.  Copy both of the "Data to sign" to compare, and you can find failed validation items.

   ![Data to Sign Comparision](./image/security-config/sign_data_compare.png)


  6.  Reset the logger level of "**...platform.data.protection.impl.ModelSignServiceImpl**" under the application (take proposal for example) to be **INFO**.


#### Hash Calculation Switch in Configuration

By default, the platform will enable the hash calculation check. If you do need to disable this feature, please set the global parameter accordingly.

> Go to Global Parameter, and find **BusinessConfigTable**, override the config item **calculate.hash.validate.ignored** value to **Y**

![Switch Hash Vlidation](./image/security-config/security_config_calculate_hash_check.png)



## Policy All Sensitive Data Removal (General Insurance Only)

### Background

In GDPR regulation, when a customer chooses not to go with the insurer anymore and wants to clean up all their sensitive data, system needs to automatically remove all customer related sensitive data e.g., ID number or email address from the system.

To cater for such requirement, there's a specific sensitive policy data removal API exposed from InsureMO GI. Project team can configure what are the fields required to clean and call the API to clean up them.

Now this feature is only available for GI policy domain and users need to contract InsureMO support team if they want to clean up data for other domains such as claim or BCP. 


### Sensitive Field Configuration

* GdprDbDataRemoveConfig: Data to remove from policy
   
| Column Name | Description |
| ------ | ------ |
| ConfigId | Pk, sequence, should be unique. |
| ServiceName | The service own the table. | 
| IndexName | Table name. |
| FieldName | For fix field, the field name is the db table column name, for dynamic field the field name is DD field name. |
| AlternativePolicyIdField | Default policyId field name is PolicyId, If different then config here. |
| AlternativeFieldValueForRemove | Default is null, configure here for different value. |



* GdprEsDataRemoveConfig: Data to remove from ES

| Column Name | Description |
| ------ | ------ |
| ConfigId | Pk, sequence, should be unique. |
| ServiceName | The service own the table. | 
| TableName | Table name. |
| FieldName | For fix field, the field name is the db table column name, for dynamic field the field name is DD field name. |
| IsFieldInJson | Y-Yes, N-No. For fixed field, set to N-No; for dynamic field, set to Y-Yes. |
| JsonFieldName | Default json field name is `DYNAMIC_FIELD`, if different then config here. |
| JsonNodeDDType | Use @type field value to filter nodes in json, if null then filter nodes without @type field. Can supports wildcard char. |
| AlternativePolicyIdField | Default policyId field name is PolicyId, If different then config here. |
| AlternativeFieldValueForRemove | Default is null, configure here for different value. |
| PkFieldNames | PK field name, if multiple then split by ",".|


Take the customer data for example, set the field to del as below:

![GdprDbDataRemoveConfig](./image/security-config/gdpr_GdprDbDataRemoveConfig_customer.png)

To delete the field in ES as below:

![GdprEsDataRemoveConfig](./image/security-config/gdpr_GdprEsDataRemoveConfig.png)

### Sample API

#### URL

`{{server}}/{{apigw-platform-url}}/proposal/gdpr/v1/removeGdprData?serviceName=proposal-v2&policyId={{PolicyId}}`


#### load policy to check the data 

`{{server}}/{{apigw-platform-url}}/proposal/v1/load?policyNo={{PolicyNo}}&withCodeDesc=Y`

* Before

```json
"PolicyCustomerList": [
        {
            "@pk": 10832560004,
            "@type": "PolicyCustomer-PolicyCustomer",
            "Age": 34,
            "BusinessObjectId": 1000000290,
            "CustomerName": "Apitest202111",
            "DateOfBirth": "1988-06-08",
            "Email": "1xxxxxxxxx5@insuremo.com",
            "IdNo": "0629",
            "IdType": "1",
            "IsInsured": "N",
            "IsOrgParty": "N",
            "IsPolicyHolder": "Y",
            "Mobile": "138****2345",
            "ParentElementType": "POLICY",
            "ParentPolicyElementId": 10832560002,
            "PolicyElementId": 10832560004,
            "PolicyId": "10832560002,B935F80F98C687B54DEEDEB2A3E1F810",
            "PolicyStatus": 2,
            "SequenceNumber": 1,
            "TempData": {
                "Mask-IdNo": "7TyIEK6o+ecn3L9/3YHLAw==",
                "IdNo": "7TyIEK6o+ecn3L9/3YHLAw==",
                "MaskAfter-IdNo": "0629",
                "Mask-Mobile": "6y9TS0hsfg0DLf1NUbKTcA==",
                "Mobile": "6y9TS0hsfg0DLf1NUbKTcA==",
                "MaskAfter-Mobile": "138****2345"
            },
            "Address1": "Address1-Address1-Address1",
            "CustomerType": "IndiCustomer",
            "Gender": "F",
            "Gender_CodeDesc": "Female",
            "IdType_CodeDesc": "CPF",
            "IsInsured_CodeDesc": "No",
            "IsOrgParty_CodeDesc": "No",
            "IsPolicyHolder_CodeDesc": "Yes",
            "PolicyStatus_CodeDesc": "Effective"
        }
    ],
```



* After


```json
"PolicyCustomerList": [
        {
            "@pk": 10832560004,
            "@type": "PolicyCustomer-PolicyCustomer",
            "Age": -1,
            "BusinessObjectId": 1000000290,
            "CustomerName": "[DEL]",
            "Email": "[DEL]",
            "IdNo": "0629",
            "IdType": "1",
            "IsInsured": "N",
            "IsOrgParty": "N",
            "IsPolicyHolder": "Y",
            "Mobile": "[DEL]",
            "ParentElementType": "POLICY",
            "ParentPolicyElementId": 10832560002,
            "PolicyElementId": 10832560004,
            "PolicyId": "10832560002,B935F80F98C687B54DEEDEB2A3E1F810",
            "PolicyStatus": 2,
            "SequenceNumber": 1,
            "Address1": "[DEL]",
            "CustomerType": "IndiCustomer",
            "Gender": "[DEL]",
            "TempData": {
                "Mask-IdNo": "7TyIEK6o+ecn3L9/3YHLAw==",
                "IdNo": "7TyIEK6o+ecn3L9/3YHLAw==",
                "MaskAfter-IdNo": "0629",
                "Mask-Mobile": "evF8IGdhmKhbQZnxc6MNkg==",
                "Mobile": "evF8IGdhmKhbQZnxc6MNkg==",
                "MaskAfter-Mobile": "[DEL]"
            }
        }
    ],
```


We can see 
* The **CustomerName**, **Email**, **Mobile**,  **Address1** and **Gender** are removed and returned with "**[DEL]**".
* The **Age** is returned with value **-1**.
* the **DateOfBirth** is totally removed, and not returned.

![GdprDbDataRemoveConfig check](./image/security-config/gdpr_GdprDbDataRemoveConfig_customer_check.png)


#### query policy to check the data

`{{server}}/{{apigw-platform-url}}/proposal/v1/load?policyNo={{PolicyNo}}&withCodeDesc=Y`

* Before

```json
{
    "GroupField": "entity_type",
    "PageNo": 1,
    "PageSize": 20,
    "Results": [
        {
            "EsDocs": [
                {
                    "AgentCode": "PTY10000051656007",
                    "EffectiveDate": "2021-04-22",
                    "ExpiryDate": "2022-04-21T23:59:59",
                    "FirstDataEntryDate": "2024-09-12T11:27:50",
                    "FullOrgCode": "10002\n10001",
                    "IdNo": "0629",
                    "IdType": "1",
                    "InsuredCustomerNo": "",
                    "InsuredIdNo": "IdNo",
                    "InsuredIdType": "1",
                    "InsuredName": "Test1",
                    "OrgCode": "10002",
                    "PolicyHolder": "Apitest202111",
                    "PolicyHolderIDNo": "0629",
                    "PolicyId": "10832540003,4F782C3E7BFA433F1620E0F15BC5D759",
                    "PolicyStatus": 1,
                    "PolicyType": "1",
                    "ProductCode": "TBTI",
                    "ProductId": 351925022,
                    "ProductName": "Oversea Travel (PBU for Auto Test)",
                    "ProposalDate": "2021-04-22",
                    "ProposalNo": "PABTBTI0001325386",
                    "ProposalStatus": 1,
                    "RiskName": "Test1",
                    "entity_id": "10832540003,4F782C3E7BFA433F1620E0F15BC5D759",
                    "entity_type": "Policy",
                    "id": "Policy_10832540003",
                    "index_time": "2024-09-12T03:27:50.470Z",
                    "index_timestamp": 1726111670220,
                    "owned_agent_code": "PTY10000051656007",
                    "owned_org_code": "10002",
                    "tenant_code": "xxxxxx",
                    "TempData": {
                        "Mask-IdNo": "7TyIEK6o+ecn3L9/3YHLAw==",
                        "IdNo": "7TyIEK6o+ecn3L9/3YHLAw==",
                        "MaskAfter-IdNo": "0629",
                        "Mask-InsuredIdNo": "Jmf1vghdrScrTdpdeyKu7w==",
                        "InsuredIdNo": "Jmf1vghdrScrTdpdeyKu7w==",
                        "MaskAfter-InsuredIdNo": "IdNo"
                    }
                }
            ],
            "GroupTotalNum": 1,
            "GroupValue": "Policy"
        }
    ],
    "Total": 1
}
```

* After


```json
{
    "GroupField": "entity_type",
    "PageNo": 1,
    "PageSize": 20,
    "Results": [
        {
            "EsDocs": [
                {
                    "AgentCode": "PTY10000051656007",
                    "EffectiveDate": "2021-04-22",
                    "ExpiryDate": "2022-04-21T23:59:59",
                    "FirstDataEntryDate": "2024-09-12T11:27:50",
                    "FullOrgCode": "10002\n10001",
                    "IdNo": "0629",
                    "IdType": "1",
                    "InsuredCustomerNo": "",
                    "InsuredIdNo": "[DEL]",
                    "InsuredIdType": "1",
                    "InsuredName": "[DEL]",
                    "OrgCode": "10002",
                    "PolicyHolder": "Apitest202111",
                    "PolicyHolderIDNo": "[DEL]",
                    "PolicyId": "10832540003,4F782C3E7BFA433F1620E0F15BC5D759",
                    "PolicyStatus": 1,
                    "PolicyType": "1",
                    "ProductCode": "TBTI",
                    "ProductId": 351925022,
                    "ProductName": "Oversea Travel (PBU for Auto Test)",
                    "ProposalDate": "2021-04-22",
                    "ProposalNo": "PABTBTI0001325386",
                    "ProposalStatus": 1,
                    "RiskName": "Test1",
                    "entity_id": "10832540003,4F782C3E7BFA433F1620E0F15BC5D759",
                    "entity_type": "Policy",
                    "id": "Policy_10832540003",
                    "index_time": "2024-09-12T03:27:50.470Z",
                    "index_timestamp": 1726111670220,
                    "owned_agent_code": "PTY10000051656007",
                    "owned_org_code": "10002",
                    "tenant_code": "xxxxxx",
                    "TempData": {
                        "Mask-IdNo": "7TyIEK6o+ecn3L9/3YHLAw==",
                        "IdNo": "7TyIEK6o+ecn3L9/3YHLAw==",
                        "MaskAfter-IdNo": "0629",
                        "Mask-InsuredIdNo": "evF8IGdhmKhbQZnxc6MNkg==",
                        "InsuredIdNo": "evF8IGdhmKhbQZnxc6MNkg==",
                        "MaskAfter-InsuredIdNo": "[DEL]"
                    }
                }
            ],
            "GroupTotalNum": 1,
            "GroupValue": "Policy"
        }
    ],
    "Total": 1
}
```

We can see the **InsuredIdNo**, **InsuredName** and **PolicyHolderIDNo** are removed and returned with "**[DEL]**".

![GdprEsDataRemoveConfig](./image/security-config/gdpr_GdprEsDataRemoveConfig_check.png)

## Q&A

### My client is on EU and ask for GDPR compliance. What can I do to ensure it?

Basically, GDPR focus on personal sensitive data protection in different levels:

1. Sensitive data exposure in API to be masked - InsureMO has API masked capability.
2. Sensitive data storage in DB to be encrypted - InsureMO has data encryption capability for DB/ES/log.
3. Sensitive data removed totally upon customer request - Depending on different business domains and InsureMO has relevant API to remove data.

All InsureMO capabilities are mentioned in this chapter. A combination of them can ensure personal data will never be exposed to external parties except below scenarios:

1. Client side using fields not in the mask field scope — Clients need to review the field list against your own requirement and make sure they are included.
2. Client side actively calling unmask API — Clients can choose not to invoke API and not grant API authority for this API.
3. Client side using PAT token to call API — Clients can choose not to use PAT token or if you want to use, add specific parameter for BFF to enable mask or call our mask API to mask without BFF.
4. Client side requesting platform team to switch off mask or encryption feature — Now all mask is open by default and encryption is off by default in non-UAT/PROD environment. 

Since this mask and encryption feature has already been enabled in non UAT/PROD environment, you can test API and database to verify whether it’s so. If the result of verification shows ok, you can ask InsureMO support team to enable it in any UAT or PROD. Note that you need to be careful as activation of encryption will mean that field cannot support fuzzy search and no report/data patch on the field. 

Also, as there might be some log that contains personal information, users need to make sure log encryption is also switched on according to this document.





