本文描述的是2005年在一个项目中采用Oracle VPD技术解决同一个OU下按照不同办事处屏蔽销售订单的解决方案。
VPD技术提供了数据库对象(表,同义词,视图)行级别访问的控制。关于VPD更多的信息不在此进行描述,有兴趣的同学可以查看Oracle Database Security Guide中的Using Virtual Private Database to Implement Application Security Policies章节。
一,业务需求
- 同一个OU下按照不同的办事处进行业务的操作
- 各办事处信息必须保证独立
- 办事处的人员只能对自己的客户下单
二,功能需求
由于上面的业务需求,以致需要有下面的功能需求
- 给不同的办事处设置不同的订单类型,并保证特定的订单类型只有特定的办事处所有,其它的订单类型为所有办事处拥有,对订单类型进行屏蔽
- 办事处只能使用自己的价目表来进行销售,需要对价目表进行办事处的屏蔽
- 办事处只能使用自己的客户及客户地址进行销售,需要对客户及客户地址进行屏蔽
- 办事处只能够查看和修改自己的订单信息,需要对销售订单进行屏蔽
三,技术方案
一般情况下,这样的需求在Oracle EBS二次开发中都会被认为是“不合理”的需求,如果顾问最终答应了这样的需求,对于开发人员首先会想到的就是修改相关的Form界面以及一些数据库视图的定义,但在Oracle EBS的二次开发规范中,最重要的一条原则就是不能修改标准功能的程序;而在这个案例中,即使我们修改了一些表面上的功能,如销售订单界面,客户信息查询等等,但是很有可能很多标准的报表或者程序都是没有进行相关屏蔽的,这样对于这项开发来说就是一个不可能完成的任务,也是一个风险非常大,以至于留下很多陷阱的开发。
这时候我想到要是有一个办法,能够从基表动态添加一个条件,将其按照办事处进行条件的筛选,那岂不是“釜底抽薪”,彻底解决了我的问题?
VPD通过安全策略(Policy)来动态返回一个条件(WHERE子句)来使得从行级别进行数据的屏蔽,将一个或多个安全策略与表或视图关联后,就实现了Oracle数据库的VPD功能。对带安全策略的表进行直接或间接访问时,数据库将调用一个实施该策略的函数,策略函数返回一个访问的条件(WHERE 子句),即谓词。应用程序将它附加到用户的 SQL 语句后面,从而动态修改用户的数据访问权限。
一般情况下,开发人员通过编写一个存储过程将 SQL 谓词附加到每个 SQL 语句(用于控制该语句的行级别访问权限)来实现VPD。VPD确保了无论用户以何种方式访问数据(通过应用程序、报表编写工具或SQL*Plus),都将强制实施访问权限控制策略,所以如果开发人员对oe_order_headers_all添加了一个“header_id > 1000 ”的条件谓词,那当任何程序以select * from oe_order_headers_all进行数据访问的时候,实际上真正运行的SQL语句则是select * from oe_order_headers_all WHERE header_id > 1000 ,因此通过动态生成WHERE谓词条件从而达到动态控制数据的安全。
下面是技术解决方案的主要步骤:
- 定义值集来维护办事处信息
- 定义一个预制文件来表明办事处身份
- 为每个办事处定义不同的职责,并将办事处的值分配给上面定义的预制文件
- 开启销售订单类型、价目表、客户和客户地址的描述性弹性域来记录办事处信息,将基础信息归类给各办事处,如果不设置办事处则表明所有办事处同时拥有,并写到个描述性弹性域段attribute1中(将这些基础数据按照办事处进行分类)
- 对销售订单类型、价目表、客户和客户地址的数据库表添加VPD安全策略,使attribute1字段受到办事处的限制(解决在销售订单界面或者其它地方引用这些对象是按照办事处屏蔽信息)
- 对销售订单头和行的表添加插入触发器,将办事处的信息写入 attribute1中,表明销售订单信息属于哪个办事处(解决录入数据的归属问题)?
- 对销售订单头和行的表添加VPD安全规则,使attribute1字段受到办事处的限制(解决订单查询时的屏蔽问题)
四,技术实现
很多技术的实现省略了,主要描述VPD策略如何实现:
首先是编写WHERE谓词条件:
|
FUNCTION oe_order_header_predicate( oowner IN VARCHAR2, ojname IN VARCHAR2) RETURN VARCHAR2 IS l_branch_code VARCHAR2(10); BEGIN -- 得到办事处预制文件的值 l_branch_code := fnd_profile.VALUE('CUX_BRANCH_CODE') -- if the responsibility not set the CUX_BRANCH_CODE profile's value -- and all data are visible; otherwise, the data is restricted to profile IF l_branch_code IS NOT NULL THEN RETURN ('attribute1 = ' || l_branch_code ) ; ELSE RETURN ('1=1'); END IF; END oe_order_header_predicate; |
oe_order_header_predicate过程的两个参数类型是固定的,同时本文演示中将它放在cux_vpd_policy数据库包中
下面是注册VPD策略:
|
BEGIN dbms_rls.add_policy( object_schema => 'apps', object_name => 'oe_order_headers_all', policy_name => 'bsm_oe_order_headers_rls1', function_schema => 'apps', policy_function => 'cux_vpd_policy.oe_order_header_predicate'); END; |
上面只是演示了为销售订单头表添加VPD策略,其它的内容就留给读者自己来完成,如有任何问题或者更详细的内容,请提出来交流。