CRM实施随笔——Dynamics CRM有关GDPR数据脱敏问题处理
首先什么是GDPR?
GDPR是 (The European) General Data Protection Regulation 的缩写,即通用数据保护条例。是欧盟议会和欧盟理事会在?2016?年?4?月通过,在?2018?年?5?月开始强制实施的规定。
GDPR 意义在于推动强制执行隐私条例,规定了企业在对用户的数据收集、存储、保护和使用时新的标准;另一方面,对于自身的数据,也给予了用户更大处理权。
总而言之,对OP部署的国际化CRM项目(数据库部署在中国)的影响就是欧洲数据联系人等实体敏感数据(电话、邮箱、地址、年龄等),不能直接明文存储在数据库中。否则就会有相关部门上门请喝茶。。
常见解决方案
方案一 敏感字段数据加密后存储在数据库中。
在Plugin中实现逻辑,Create或者Update之前,将敏感字段加密,然后存储在数据库中;Retrieve和RetrieveMultiple后,对加密字段进行解密,然后显示
方案二 敏感字段分离存储在海外单独部署的数据库中,实现敏感数据不出境。
a.确定需要脱敏实体、字段,以及何种条件下需要将数据存储在海外数据库
b.实现接口读写海外数据库
c.Create或者Update之前,将敏感字段加密调用接口存储到海外数据库,同时清空上下文实体中的字段数据,防止保存到本地数据库;Retrieve和RetrieveMultiple后调用接口对当前上下文的敏感字段重新赋值
方案二具体实现【例】
1.联系人实体根据联系人类型判断,当联系人类型为海外联系人时,联系方式、邮箱等字段存在海外数据库,其他不做处理
2.实现接口 WebAPI
////// 保存联系人 /// /// [HttpPost, Route("SaveContact")] public void SaveContact(Dictionary<string,object> contact) { new ContactCommand().SaveContact(contact); } /// /// 查询联系人 /// /// [HttpGet, Route("GetContact")] public Contact GetContact(Guid id) { return new ContactCommand().GetContact(id); } /// /// 获取联系人列表 /// /// /// [HttpPost, Route("GetContactList")] public List GetContactList(List ids) { return new ContactCommand().GetContactList(ids); }
3.实现Create/Update之前逻辑
PreCreateAndUpdate.cs
protected override void ExecutePlugin() { try { //判断实体是否满足脱敏条件 if(CommonHelper.CheckOverseaContact(Entity, OrganizationServiceAdmin)) { //调用接口保存数据 //Entity.Id = Guid.NewGuid(); var dic = new Dictionary<string, object>(); dic.Add("id", Entity.Id.ToString()); //职务 string if (Entity.Contains("new_duty")) { dic.Add("new_duty", Entity.GetAttributeValue<string>("new_duty")); }//联系人属性 int if (Entity.Contains("new_contactsattribute")) { dic.Add("new_contactsattribute", Entity.GetAttributeValue("new_contactsattribute")?.Value); } //是否潜在客户 bool if (Entity.Contains("new_islatentcontacts")) { dic.Add("new_islatentcontacts", Entity.GetAttributeValue<bool>("new_islatentcontacts")); }//保存至海外数据库 InterfaceCommonHelper.Post("api/Contact/SaveContact", "DataMask", JsonHelper.Serialize(dic), OrganizationServiceAdmin); //清空敏感子弹 //职务 string Entity["new_duty"] = null; //称谓 int Entity["new_appellation"] = null;//是否潜在客户 bool Entity["new_islatentcontacts"] = null; } } catch (Exception ex) { throw new InvalidPluginExecutionException(ex.Message); } }
4.实现查询逻辑
查询需要考虑两个部分一个是单独查询数据retrieve,另一种就是retrievemultiple。需要注意的是在特定类型联系人下才调用接口。
PostRetrieve.cs
protected override void ExecutePlugin() { if (Context.MessageName.ToLower() == "retrieve") { Entity entity = Context.OutputParameters["BusinessEntity"] as Entity; //包含客户类型 if (entity.Contains("new_contactlogtype") && entity["new_contactlogtype"] != null) { if (entity.GetAttributeValue("new_contactlogtype").Value != 5 && entity.GetAttributeValue ("new_contactlogtype").Value != 6) { return; } } else { var contactObj = OrganizationServiceAdmin.Retrieve("contact",entity.Id,new Microsoft.Xrm.Sdk.Query.ColumnSet("new_contactlogtype")); if (contactObj.GetAttributeValue ("new_contactlogtype").Value != 5 && contactObj.GetAttributeValue ("new_contactlogtype").Value != 6) { return; } } //读取接口记录 var contactStr = InterfaceCommonHelper.Get("api/Contact/GetContact?id=" + entity.Id.ToString(), "DataMask", OrganizationServiceAdmin); var result = JsonHelper.Deserialize (contactStr); if (result.ErrorCode == 0) { var contact = JsonHelper.Deserialize (result.Data.ToString()); //职务 string entity["new_duty"] = contact.new_duty; //称谓 int if (contact.new_appellation > 0) entity["new_appellation"] = new OptionSetValue(contact.new_appellation); //联系人属性 int if (contact.new_contactsattribute > 0) entity["new_contactsattribute"] = new OptionSetValue(contact.new_contactsattribute); //是否潜在客户 bool entity["new_islatentcontacts"] = contact.new_islatentcontacts; } } if (Context.MessageName.ToLower() == "retrievemultiple") { EntityCollection ec = Context.OutputParameters["BusinessEntityCollection"] as EntityCollection; var ids = (from e in ec.Entities select e.Id).ToList(); //调用接口 var contactListStr = InterfaceCommonHelper.Post("api/Contact/GetContactList", "DataMask", JsonHelper.Serialize(ids), OrganizationServiceAdmin); var contactListResult = JsonHelper.Deserialize (contactListStr); if (contactListResult.ErrorCode == 0) { var list = JsonHelper.Deserialize >(contactListResult.Data.ToString()); if (list.Count <= 0) { return; } foreach (var ent in ec.Entities) { var contact = (from c in list where c.id == ent.Id select c).FirstOrDefault(); if (contact != null) { //文本 ent["new_duty"] = contact.new_duty; //邮箱 ent["emailaddress1"] = contact.emailaddress1; //电话 ent["new_phone"] = contact.new_phone; //picklist if (contact.new_appellation > 0) { ent["new_appellation"] = new OptionSetValue(contact.new_appellation); } } } } } }