Question
· Jun 9, 2017

Caché Classes: How to Make a View on a Class to Manage Property Level Security?

Hi, Community!

Suppose I have class A with properties P1 and P2.

I want to introduce class B, which would have same records as Class A, but only  one property - P2.

What is the easiest  way to manage it assuming that I would like to use Class A to add records and be available for any operations to Users with Role A.

And I would like to introduce class B for Users with role B for read-only access.  Preferably they shouldn't even be aware of Class A and P1 existence .

What is the easiest way to introduce it and manage it?

Use some proxy-classes?  Property-level security?

Or to Introduce usual class B with only property P2 and manage the update operations together with Class A?

Discussion (10)1
Log in or sign up to continue

If you only need SQL access, then will be easier to create a view (CREATE VIEW), if need both, then - %CacheSQLStorage, e.g.:

Class demo.A Extends %Persistent
{

Property P1;

Property P2;

ClassMethod Fill()
{
  ..%KillExtent()
  
  i=1:1:3 {
    t=..%New()
    t.P1="P1_"_i
    t.P2="P2_"_i
    t.%Save()
  }
}

Storage Default
{
<Data name="ADefaultData">
  <Value name="1">
    <Value>%%CLASSNAME</Value>
  </Value>
  <Value name="2">
    <Value>P1</Value>
  </Value>
  <Value name="3">
    <Value>P2</Value>
  </Value>
</Data>
<DataLocation>^demo.AD</DataLocation>
<DefaultData>ADefaultData</DefaultData>
<IdLocation>^demo.AD</IdLocation>
<IndexLocation>^demo.AI</IndexLocation>
<StreamLocation>^demo.AS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

}

Class demo.B Extends %Persistent FinalStorageStrategy = Default ]
{

Parameter READONLY = 1;

Property P2;

Storage Default
{
<SQLMap name="BDefaultData">
  <Data name="P2">
    <Piece>3</Piece>
  </Data>
  <Global>^demo.AD</Global>
  <RowIdSpec name="1">
    <Expression>{L1}</Expression>
    <Field>ID</Field>
  </RowIdSpec>
  <Subscript name="1">
    <Expression>{ID}</Expression>
  </Subscript>
  <Type>data</Type>
</SQLMap>
<StreamLocation>^demo.AS</StreamLocation>
<Type>%CacheSQLStorage</Type>
}

}

Result:

USER>##class(demo.A).Fill()
 
USER>d $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
 
The command prefix is currently set to: >.
Enter q to quit, ? for help.
USER>>select * from demo.A
1.      select * from demo.A
 
ID      P1      P2
1       P1_1    P2_1
2       P1_2    P2_2
3       P1_3    P2_3
 
3 Rows(s) Affected
statement prepare time(s)/globals/lines/disk: 0.1426s/46110/260143/45ms
          execute time(s)/globals/lines/disk: 0.0004s/16/809/0ms
                          cached query class: %sqlcq.USER.cls12
---------------------------------------------------------------------------
USER>>select * from demo.B
2.      select * from demo.B
 
ID      P2
1       P2_1
2       P2_2
3       P2_3
 
3 Rows(s) Affected
statement prepare time(s)/globals/lines/disk: 0.0696s/44550/243602/0ms
          execute time(s)/globals/lines/disk: 0.0002s/4/619/0ms
                          cached query class: %sqlcq.USER.cls13
---------------------------------------------------------------------------
USER>>quit
 
USER>##class(demo.B).%OpenId(3).P2
P2_3

Is there any way to "generate" compatible storage? And why SQLStorage? Why not default CachéStorage, but copy-pasted from Class A?

Unfortunately, to use %CacheStorage will not work, since at compile occurs the error:

ERROR #5564: Storage reference: '^demo.AD' used in 'demo.B.cls' is already registered for use by 'demo.A.cls'
  > ERROR #5030: An error occurred while compiling class 'demo.B'

Indeed, thank you:

Class demo.B Extends %Persistent Final ]
{

Parameter MANAGEDEXTENT As INTEGER [ Constraint "0,1"Flags = ENUM ] = 0;

Parameter READONLY = 1;

Property P2;

Storage Default
{
<Data name="BDefaultData">
  <Value name="3">
    <Value>P2</Value>
  </Value>
</Data>
<DataLocation>^demo.AD</DataLocation>
<DefaultData>BDefaultData</DefaultData>
<IdLocation>^demo.AD</IdLocation>
<IndexLocation>^demo.AI</IndexLocation>
<StreamLocation>^demo.AS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

}

Hi!

It looks like you are trying to implement security on your class model instead of just configuring it. I think you only need a single class with all the properties. Then you will give user A full access to the table by configuring this user on a Role that gives him INSERT, DELETE, UPDATE, SELECT privileges. 

User B would be assigned to another role that would give it SELECT privilege only.

And if User B can only see a subset of columns from your table, then configure row level security using the Role information on $Role. InterSystems documentation here explains row level security configuration very clearly.