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?

  • 0
  • 0
  • 387
  • 7
  • 3

Answers

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

Thanks, Vitaly!

That looks exactly like what I want. I'll give it a try. Except with not very clear magic with SQLStorage. 

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

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>
}

}

VIews work for SQL access. I really need a kind of view, but for classes paradigm: so I could have access to Class B property P2 with Caché object access.

 

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.

 

You are right Eduard. Column level security would be enough. It is even simpler!