In my last post, I talked about the concept of row level security impelementation. Performance issue will gradually arise while number of rows in MyData increases. This is because is_member function evaluate every row in MyData table and check whether it’s the row should be returned. In order to make index becomes Search-able, we will have to change the structure of the view.

Now let me assume that all the objects created in my last post exist in the database. Let’s add one more table to associate RoleID with database principles and modify the view

/*create table MyData
(
	MyDataID int identity(1,1) primary key,
	Description varchar(100),
	RoleID int
)
go*/
create table RowPrincipleMap
(
	RoleID int identity(1,1) primary key,
	PrincipleID int not null unique
)
set identity_insert RowPrincipleMap on
go
insert into RowPrincipleMap(RoleID, PrincipleID) values(5,5)
insert into RowPrincipleMap(RoleID, PrincipleID) values(6,6)
go
set identity_insert RowPrincipleMap off
go
alter view vMyData
as
select d.*
from MyData d
	inner join RowPrincipleMap r on d.RoleID = r.RoleID
where is_member(user_name(r.PrincipleID)) =1
go

The intention of this is to let is_member function to run against very small table, RowPrincipleMap rather than a table potentially huge in the future. Let’s verify validity of this change

execute as user = 'user1'
select * from vMyData
/*
MyDataID    Description RoleID
----------- ----------- -----------
2           value1      5

(1 row(s) affected)

*/
revert
go
execute as user = 'user2'
select * from vMyData
/*
MyDataID    Description  RoleID
----------- ------------ -----------
1           value1       6

(1 row(s) affected)

*/
revert
go
execute as user = 'user3'
select * from vMyData
/*
MyDataID    Description RoleID
----------- ----------- -----------
1           value1      6
2           value1      5

(2 row(s) affected)

*/
revert
go
execute as user = 'user4'
select * from vMyData
/*
MyDataID    Description RoleID
----------- ----------- -----------
1           value1      6
2           value1      5

(2 row(s) affected)

*/
revert
go

cool! everything is as excepted.

Now we will have the full control over the data reading from the view. What about writing?

execute as user = 'user1'
select * from vMyData
update vMyData set description = 'zzz'
select * from vMyData
/*
MyDataID    Description RoleID
----------- ----------- -----------
2           zzz         5

(1 row(s) affected)

(1 row(s) affected)

MyDataID    Description RoleID
----------- ----------- -----------
2           zzz         5

(1 row(s) affected)
*/

revert

Ok, we can update the table from the view. This definitely is an enhancement on the view. In previous version of SQL server, probably SQL Server 2000, if a view has a inner join or outer join, the view will not be updatable. Now let’s do another test

execute as user = 'user1'
select * from vMyData
update vMyData set RoleID = 6
select * from vMyData
/*
MyDataID    Description RoleID
----------- ----------- -----------
2           zzz         5

(1 row(s) affected)

(1 row(s) affected)

MyDataID    Description RoleID
----------- ----------- -----------

(1 row(s) affected)
*/

revert

execute as user = 'user2'
select * from vMyData
/*
MyDataID    Description RoleID
----------- ----------- -----------
1           value1      6
2           zzz         6

(2 row(s) affected)
*/
-- set it back
update vMyData set RoleID = 5 where MyDataID = 2
revert

I update the RoleID under user1 security context to a RoleID that user1 should not have access to, which lead the row to be removed from the view. This can be either a feature or an unwanted bug. If it’s considered as a bug, we should prevent this from happening. The simplest way of doing that is to place WITH CHECK OPTION in the view definition.

execute as user = 'user1'
--select * from vMyData
update vMyData set RoleID = 6
/*
Msg 550, Level 16, State 1, Line 1
The attempted insert or update failed because the target view either specifies WITH CHECK OPTION or spans a view that specifies WITH CHECK OPTION and one or more rows resulting from the operation did not qualify under the CHECK OPTION constraint.
The statement has been terminated.

*/
revert
go

See, this problem is resolved by built-in functionalities without any envolvement of customized codes. For update, WITH CHECT OPTION prevents rows invisible. View itself can prevent deleting rows outside of the view. What about insert? When inserting a row through the view, you will have to provide a RoleID. If you have access to the data from multiple departments, what RoleID you would pick for the new record? Let’s talk about it in my next post.

Row Level Security (2)

You May Also Like

Leave a Reply

Your email address will not be published. Required fields are marked *

C# | HTML | Plain Text | SQL | XHTML | XML | XSLT |

This site uses Akismet to reduce spam. Learn how your comment data is processed.