As we saw in the last episode there might be access restrictions on the Metadata Object level, as well as on the attributes level. Is there such a thing as a restriction on specific rows in the table? Of course, there is.
This is what this episode is all about.
To read this metadata object records, we need to run a query looking something like this. Denying access rights to this metadata object effectively bans it from being used in queries, and if I try to run such a query I’ll get an access violation error. If there is access to the object as a whole but some of its attributes are off-limits, I’ll get the same outcome.
As for the record level security, it works quite differently. If I want a user to see only records with Attribute1 values less than 5, I have to add to the query’s the WHERE clause with this condition inside.
And this is exactly what the Platform does when we use RLS conditions in roles: adds a condition to all the queries featuring this metadata object.
If the user belongs to several roles with RLS, all RLS conditions are added to the WHERE clause and connected with the OR operator. It means that adding another role to the same user can open up some records that weren’t accessible before.
Which is true, of course, for all types of access restriction. Adding new roles, if anything, extends the user's mandate, because new roles can bring new permissions with it.
Now. What happens if there are other, not RLS-related, conditions applied to a query? They simply get connected to the same WHERE clause with the AND operator, like this.
OK. Let’s now put the theory aside and consider this real-life task I recently stumbled upon.
The thing is that I caught my imaginary cashiers teasing each other by changing each other's name in the Cashiers catalog. To enforce a zero tolerance policy against such childish behaviour, I’m going to restrict these funny guys access in a way that they can see any cashier info but can change only their own records.
And this is how it’s done. I’m creating a new role called Cashier and giving it all access rights for a starter.
Now, this is a catalog I need to set up the record level security for. And this is the frame where it’s done. RLS can be set only for these four database-level operations only for these four data access rights, and, more importantly, it’s set up separately for each one of them.
If I turn this access right off, this role won’t be able to write cashiers at all. If I turn it on the writing will be permitted for any records. But this right can be limited to only the records meeting whatever condition I want to write down here.
Not sure what you can and cannot write here? Run the Query builder, and here you go. These are all the Cashier catalog’s attributes and here are all other tables in case you want to use them in the condition.
Now, let’s try this rather meaningless condition for the sake of experiment. This way we limit the updatability to the only cashier record - the one with the last name Blue. To check how it works, we need to save the app, add a new user, include it into the Cashier role and run the app under this new user name.
Now, if I want to change Daniel’s record, this is what I get because the condition is not met. And if I try to change Agnes’s, it will work no problem, because the Last Name field’s value equals Blue.
One more interesting question. What happens if I change this record so it doesn’t satisfy the condition anymore? This is what.
It means that the Platform requires both old and new versions of the record to meet the RLS condition. The same is true for any other record in the catalog. If the current version doesn’t satisfy the RLS condition, I cannot change it in any way, even if the new version does meet it.
Now, let’s get back here and write the actual condition we need. When we need to make sure that a user can edit the only records - one belonging to him or her, this is the way to go.
Of course, we cannot use anything but the catalog item reference to identify the only record we allow to edit - the one with this specific reference value. Which immediately raises a question: how to set this parameter value?
This condition will be added by the Platform to all Cashier catalog queries all over the app. There is no way we could trace all these queries and set the parameter’s value everywhere. Luckily, we don’t have too, because of these Session parameters the Platform keeps handy for us.
This is how it works. I need to create a session parameter with this exact name, set its type to Cashiers catalog reference like this, and then set its value in this event handler living inside of this session module that gets run whenever a user logs in to the application.
How is this module different from the Application module over here? Could we set the CurrentUser parameter in this OnStart event handler? No, we couldn’t. And let me show you why.
Whenever we start the client application this session module is the first one to be run. Then, it’s the
Application module’s turn. By the time this module is done executing its handlers, the application is up and running.
But running the client is not the only way to get access to the application. For example, the app can have SOAP or REST services published to the Internet. Users of these services will be required to login and pass through the same Session module’s procedures. But after that, they won’t go through the client application module, because no client is getting started. Instead they will go to the web service’s own module executing whatever code is in there.
This is why we need to set the session parameter right here - the only place any user login has to pass through.
And this is how it’s done. First of all, we need to learn what user is logging in right now. And this is the Global context object to be used for the task. Let’s peek inside, and here we go: the CurrentUser method in all its glory.
Let’s run the app and see what it returns in runtime. And here is our Agnes Blue, which is good. But here is a catch. The value we need to pass to this query parameter has to have the Cashiers catalog reference type for this comparison to work. But Agnes we have here has a very different type and lives up here in the Platform user list. While Agnes we need for our RLS condition lives in the Cashiers catalog.
This is why we need to bind Cashier catalog to the system Users list, so when we get the current system user we could find the catalog item belonging to it. This User ID thingy is a unique system user identifier that we want to use as a reference.
Therefore I just added it right here to the Cashiers catalog and then retrofit the Item form like this. I added this independent string attribute, dropped it to the form, switched on the choice button, and implemented these event handlers.
When the choice button is clicked, I read the system user list using the GetUser metгhod of the InfoBaseUsers global context object and then pass it to this event handler parameter, for the form to show it to a user.
Then, after a user makes a choice, I take the selected system user id and store it to this new catalog attribute.
Then I run the app and bind every cashier in the catalog to the system user like this.
Now I can get back to the session module and do the following. First of all, I need to turn on the privileged mode, so anyone could read the current system user, which normally requires administrative rights.
After that I can go to the Cashiers catalog and find an item having the same unique identifier as the current system user and assign this newfound cashier to the CurrentUser session parameter.
As soon as it’s done, every RLS restriction having a parameter with this exact name will automatically get this exact value.
And this is how my restriction works. I logged in under Agnes name, so when I try to edit Agnes’s record, it works like a charm. But when I try to play with others’ records, this error message is all I get.
OK. Next order of business.
As you remember, I started playing with flexible bonuses recently, and this is a new two percent bonus I came up with. It calculates a profit margin by subtracting the cost of goods sold from a selling price.
Since then, my Cashiers (who also perform merchandisers' duties) got quite competitive with each other over the produce suppliers. The thing is that each supplier “belongs” to a specific cashier, as it set up in this CompaniesByCashiers information register. Cashiers stay in touch with their contacts, and closely monitor sales deals and special offers, so at the end of the day they can buy things cheaper and maximize their bonus by reducing this cost part of this equation.
This small innovation of mine got my good old Agnes and Denny very protective over producers’ valuable contact info, and they made me promise I will restrict each cashier's access to other cashier’s companies.
And this is how it’s done. This is the role all my cashiers belong to. And this is the Companies catalog access rights for this role. For now the Cashier role has unrestricted read access to this catalog.
Let’s add a new RLS restriction and start writing the condition we need. First of all we have to select the companies belonging to the current user from this information register. This is our subquery. As for the final condition for the Companies catalog, this is what it should look like. Using this condition we grant access to all Companies catalog records whose references are in the list returned by this subquery.
And this is how it all works. Being logged in as Agnes I see only Agnes’s companies. While Danny sees only his companies. Which is good. But not ideal.
Because, for example, this is what Agnes will see instead of Infinity Foods - one of Danny’s companies.
Wouldn’t it be so much nicer if cashiers could see names of all companies regardless of what company belongs to whom? Under the condition that the contacts are still hidden from competition, of course.
So, the question is how do we apply this RLS condition to some attributes rather than to the entire object? This is how.
I’m telling the RLS that it should be applied only to the list of contacts, in which case Agnes will be able to see any company name but won’t be allowed to open any Danny’s company form.
But here is another catch. If Agnes can see Danny’s companies in the lists she can also create a Purchases document with one of them. Which is not how it should work, so, let’s get back to the Cashier role and add some new RLS restrictions to the Purchases document. These are the rights we need to restrict and this is the attribute that needs to be in the list of the current user’s companies.
Basically, we need the same restriction we use for the Companies catalog, so I’m copying it right here and replacing the reference with the Company attribute.
Next, I need to copy the same condition to the Update right over here, which makes me think: Is there any way to write this text once and then just reuse it wherever needed?
Of course, there is. These reusable conditions are called templates, and this is how they work. I’m creating a new template called ByCompany, pasting the condition over here, and now I need to do something about this attribute name.
For the Purchases document it has to be Company while for the Companies catalog it’s Ref. Therefore, instead of using any of these attribute names, I type this. It means that the template is not only reusable.
It’s also parameterizable. Anywhere in the template text I can use the number sign plus the Parameter keyword and then the number of a parameter in brackets.
Remember that a template is just a text, and all parameters are just strings that will be replaced by their values at runtime. It means that you can play with this text passing any string values as parameters anywhere.
To use this template in an RLS restriction I need to call it by name after the number sign and pass this attribute name to the template like this. Now we can use this template here and also here if we change the attribute name like this.
Let’s see if it works I logged in as Agnes and Gross Grossery is one of her companies. If she opens a Purchases document with this company, she can edit and save it. Unless, of course, she tries to change the company to some of Danny’s.
And if she opens any of Danny’s documents She cannot change anything at all, including the company the document belongs to.
This is how Record Level Security works in 1C:Enterprise.