Multitenancy Security Setup

Crafting a complex security setup within FileMaker can be a bit daunting. Possibly, because of how you interact with and establish security within FileMaker. Despite what may seem obvious, it isn't just about what FileMaker's default security provides, but how you integrate the controls which you have access too. When solution security setup and testing is the after thought, there's a lot of potential for accidental holes in the security model. However, if you take the upfront steps of setting things up from the beginning, the whole problem is a much easier nut to crack.

This video and the companion technique file will walk you through the process of establishing your security model. It offers some useful tips and tricks for working with FileMaker's cumbersome security dialogs and helps you test your security in a much more fluid way.

If you've felt like your solution was lacking in security and you knew there was more to take advantage of, then you'll certainly learn a few more things in this video. If you've never even worried about setting up security on your FileMaker solution then make sure to watch this video!

AttachmentSize
MultitenantSecurity.zip113.44 KB

Comments

Hello,
Thanks for the great video and example file; helped me clearly understand some of the security issues involved. That said, I can't seem to create new users in the solution file provided, no matter which user I log in as (FM Pro 13). It says "This user already exists and cannot be duplicated" even though the name is completely unique. I've checked the security permissions and every set says that users can be created. I can create new groups and projects just fine, but not a single user. Am I missing something obvious?

Ignore my previous comment; answered my own question -- the creationAccount field in the Users table is specified to be Unique.

Hello,
Probably a stupid question -- when specifying “creationAccount = ACCOUNTNAME” within the Custom Record Privileges calculation, does the calculation context need to be set to the same table occurrence tied to the UI layout that users actually interact with?

For example, if I have the following setup:
Table Name: Users
Table Occurrence 1: Layout_Users
Table Occurrence 2 (developer TO): @Users

and people would primarily be interacting with “LayoutUsers”, where “@Users” is just there for utility, does the security calculation context need to be based on the “LayoutUsers” table occurrence, or could it be based on “@Users” (or any other TO based on the same table)?

It seems to work if I set the context to the “@Users” table occurrence, but I’m wondering if I’m creating a security risk by doing so.

The context evaluation question, as far as I'm aware, would only be factored in when it crosses occurrences. If the evaluation is within the same TO, meaning @Users::creationAccount = @Users::ACCOUNTNAME - or - Layout_Users::creationAccount = Layout_Users::ACCOUNTNAME then it wouldn't matter if you're on a new layout based on the Users table named something totally different.

The security should see that the calculation, as being evaluated, is within the same table space and applies to whatever layout the user is on - provided that layout was based on the applicable base table.

However, proper evaluation would have to be accounted for when you establish the check within context which goes across one or more relationship(s).

Of course, the proper thing to do with ALL security related questions is to simply test and verify. In the security world it's called a pen test ( short for penetration test ).

If the data is highly critical, you might want to take the time to write scripted unit tests which can be run against the database and user facing layouts. It will seem like a lot of extra work ,but may provide the peace of mind one is searching for. - Matt

-- Matt Petrowsky - ISO FileMaker Magazine Editor

Thank you!
Can you elaborate just a bit on the statement, “However, proper evaluation would have to be accounted for when you establish the check within context which goes across one or more relationship(s).”

An example use-case / scenario would help me wrap my head around this. Do you mean, for example, something like:

Users::creationAccount = Notes::ACCOUNTNAME ?

Only the expressed context "Users::creationAccount = Notes::ACCOUNTNAME" would count when determining access... This is because the record access is controlled by the calculation. In that case the only time the record would have that level of access (view, edit or delete) is when that calc evaluated true - all others would be false - meaning no access.

So, the layout the user was on would need to be either tied to Users or Notes and have a relationship to the other.

In this case we're simply talking about a constant unstored value coming from ACCOUNTNAME - which is simply Get ( AccountName ) - which is taken from what was used when authenticating to get into the database.

It wouldn't make much sense to not have ACCOUNTNAME in each table where you need to control access against the creationAccount field. Having to go through a relationship in this case doesn't make much sense.

I would need an example where validating for record access through a relationship would be desired. I'm not saying there's no use cases for it, but it's certainly possible. The key here is simply this.

You don't add a security hole when using the custom record level access because all access is blocked UNLESS the calc evaluates to true. In your example above, you're simply narrowing the level of access instead of widening it.

Does that help? - Matt

-- Matt Petrowsky - ISO FileMaker Magazine Editor

It does help, thank you so much for the detailed replies! You have no idea the timeliness of this latest video, since I'm right at the point where tackling access control was a necessity before I could move forward. I think I was just tripped up by the part of the reply that said, "...within context which goes across one or more relationship(s)." If I just repeat the mantra, "all access is blocked UNLESS the calc evaluates to true" I think I'll be ok.

To confound myself further, I'm testing a Custom Function I created that enforces the same rights as given in the unaltered sample file that accompanied this video (not including the code that handles the cascading / inherited permissions via the "Access" field). I replaced each hard-coded calc in the Custom Privs window with a call to this function:

SecurityCheck ( )
------------
Let ( [
/* creationAccount stores the account name of the user that created the record */
~creationAccountValue = GetField ( "creationAccount" ) ;

/* ACCOUNTNAME is an unstored calc in each table that captures the currently-logged-in account name */
~ACCOUNTNAMEValue = GetField ( "ACCOUNTNAME" )
] ;

/* If the same user that's logged in is the one that created the record, they can access it. */
If ( ~creationAccountValue = ~ACCOUNTNAMEValue ; True ; False )
)

I'm using the Let instead of an If because I anticipate needing to expand on the function. When I monitor the same Let statement in the Data Viewer, it correctly outputs a 1 or a 0 for each record as appropriate, and I can't see or edit records I shouldn't be able to (i.e., it seems to behave just like the hard-coded calcs in the original sample file), so I'm assuming the Custom Function is working ok, but if you see any ridiculously stupid mistakes or gotcha's, please smack me over the head at your convenience.

Thank you again for your help & expertise. You and your videos have been a tremendous resource!

This is exactly what I'm talking about when using a custom function. The only downside is if this isn't well documented and another dev (presuming you're working on a team) doesn't know the "creationAccount" naming standard, then it will take them a bit of time to figure out why access is being denied.

For the sake of clarity in your solution I would make this suggestion. Don't embed the logic of access within a general function named "SecurityCheck()". Instead, simply use multiple custom functions (there's little cost, if any - other than organizational) with the prefix of Security. In this case you would name the CF something like SecurityUserIsRecordOwner, or something like that. The function would then simply read as follows.

GetField ( "creationAccount" ) = GetField ( "ACCOUNTNAME" )

Then in some other access calc you might have the function SecurityModifyUsers() where it reads

Get ( AccountPrivilegeSetName ) = "[Full Access]"
or Get ( AccountPrivilegeSetName ) = "My Custom Priv Set"

You can then use this

SecurityUserIsRecordOwner()
or SecurityModifyUsers()

on the Users table. ;) - Matt

-- Matt Petrowsky - ISO FileMaker Magazine Editor

In the video, you commented that using global field to store security information is a bad bad idea. I wonder why is that so? In my solution, users have no access to scripting, layout or menu. I even blocked user from using Fmpro Advanced. How is that user can ever change the global values ?

You made a comment that using global fields to store security information is a bad bad idea. Can you explain more why this is so ? In my solution, I used global fields . I hide all the menu and even prevent from accessing using the Pro Adv. I really like to know why this is a bad idea ? Thank you

I didn't mean to imply that using Global fields is a "bad" practice. It just adds a lot more overhead as opposed to using baked in functions like Get ( AccountName ) as an unstored calculation.

For each field which you use for security you're adding management/configuration overhead. You now have to make sure there is not access to that global field for however many privilege sets are using it and you have to account for scripting requirements.

Whenever you can control access based features with authentication or device specific constants such as Get ( AccountName ) or Get ( PersistentID ) you're reduced you management overhead.

As long as you're comfortable with using global fields you certainly can.

-- Matt Petrowsky - ISO FileMaker Magazine Editor

Hi, Matt:

Just wanted to point out that (on the Mac) that you can set the position of background windows (even those behind modal dialogs) just by holding down the "Command" key to click and drag on the background window menu bar to move it.

Great video, thanks!

If after a show all record how can i constrain the user to see the records with no access
Let say a rep needs to see onle record from city Chicago an new york
With your technique it is easy to see or edit only what it was approved by a super admin.
but to navigate trought the record it still show the no access on every field of the record.
Do you have trick to solve that

The key to using security is to know which accounts have access to which fields. You can, for instance, create a field called access_override and put whatever you want within it.

The security calculation can read something like If Get ( AccountName ) = Table::access_override then just that one account could see the record. If you use PatternCount() you can look at both AccountName and Get ( AccountPrivilegeSetName ). The trick is knowing that a lower level of access can't set a field which grants a higher level of access.

I hope this helps out.

Matt

-- Matt Petrowsky - ISO FileMaker Magazine Editor