In my firebase app, each user belongs to a company (a company may have many users), and data in tables is split into Table_name\COMPANY_ID\
COMPANY_ID is defined in the Users table.
Like for e.g Items table would be:
Items\<COMPANY_ID>\Item_1
Items\<COMPANY_ID>\Item_2 and so on
Users table is defined as:
Users\<AUTH_ID>\
Where AUTH_ID is the authentication id
I want to set up rules so that a user can access data only of his own company. How to make read and write rules for Items table (and all other tables too)?
Something like this should do the trick:
{
"rules": {
"Items": {
"$companyId": {
".read": "root.child('Users').child(auth.uid).child('CompanyId').val()
=== $companyId"
}
}
}
}
In words: allow the authentication user user to read a company's data, if their CompanyId matches that company.
Related
i'm new to serverless architecture in general, and i'm studying migrating my current php/mysql rest api to serverless arch.
my main concern is access control.
in certain app, i allow users to access content based on role, and groups they are assigned to "
example
role: user groups: [1,2,3] can only access content with group_id: 1 || 2 || 3
is it possible to do such access control in serverless databases like faunadb ?
It is possible to do such access control with FaunaDB and much more with the ABAC system (https://docs.fauna.com/fauna/current/security/abac.html)
Roles:
In essence you have Roles and these roles provide permissions
CreateRole({
name: "access_todos",
privileges: [{
resource: Collection("todos"),
actions: {
create: true,
update: true,
delete: true,
write: true
}
}]
})
(you might notice that this of course gives access to all groups which is not what you want, we'll get to that)
Roles can be assigned to different things:
Keys: simply make a key with that role and that key can only access the groups collection
Functions: a User Defined Function (like a stored procedure) can assume a role.
Entities in a collection or part of a collection: any entity (e.g. Users, ShareLinks, Accounts) could be assigned a role by adding a 'membership.
Roles Membership (assign roles to a database entity):
You assign a role to database entities by using the membership field.
In this case, all accounts in your database will have these privileges. You can also use a function here to filter out a certain type of account etc..
CreateRole({
name: "access_todos",
membership: [{ resource: Collection("accounts") }],
privileges: [{
resource: Collection("todos"),
actions: {
create: true,
update: true,
delete: true,
write: true
}
}]
})
Assume the identity of that entity, (get a key for that database entity):
Then that leaves us with the question: "how do we assume the identity of a user?".
We use login for that. First you create an account with a password:
Create(
Class("account"),
{
data: { email: "alice#example.com" }
credentials: { password: "secret password" },
}));
The important part is the credentials.password field which is a special field for FaunaDB. It will be encrypted and when a database entity has such a password you can use Login to assume the identity of the entity:
Login(
Index("accounts_by_email"), "alice#example.com"),
{ password: "secret password" })
Login will provide you a token and that token will now have all the rights that this account has. Or in other words all the privileges of the roles for which this database entity of the collection 'accounts' is member (and membership is defined on the role with the membership key)
The power of Role predicates and the 'Identity()' function
Ok but how do we get more fine-grained access?
Roles can have lambda predicates instead of booleans. That means in your case you could store the array of groups on the user (or vice versa) and link the account to the user.
privileges: [
{
resource: Collection("Groups"),
actions: {
read: Query(
Lambda("groupReference",
// Write your logic
)
)
}
}
]
In such a query, the lambda parameter is the reference of the entity you try to access (e.g. a group)
One question remains.. how do we check whether the user linked to an account has access to the groups? Well we use 'Identity()' for that which is an FQL function that returns the reference of the currently logged in database entity.
Note: by default you get read/write access to the entity you are logged into. Hence you do not want to store the group ids on the account since a user could in theory change these. This is why I split account and user in my explanation. We will probably change this in a future FQL version since this appears to be confusing/cumbersome.
A few good resources:
- ABAC docs: https://docs.fauna.com/fauna/current/security/abac.html
- ABAC with GraphQL: https://medium.com/fauna/abac-graphql-6e3273945b1c
- Authentication docs: https://app.fauna.com/tutorials/authentication#creating-users
We are building a complete example as we speak which I expect to appear on our blog in the coming weeks.
I am attempting to build a web app that allows users to view company policies, procedures, newsletters, and their own employee information.
I have my staff table that contains all the employee information (along with related lookup tables for things like prefix, staff grade, etc)
my app uses asp-identity for the login functionality, but I want to be able to return information from the staff table that is only relevant to that particular user.
I know it is possible to extend the ASP.net users table to include custom fields, but this doesn't really suit my goal as the staff table is used in a desktop based app by the admin team.
Add a field to your Staff table UserId for example ALTER TABLE Staff ADD UserId NVARCHAR(256) DEFAULT NULL;
Optionally, you would reference the AspnetUsers table.
Update Staff table rows in other to set UserId values to related users ids (manually or create an action to do that)
Then, In your the controller, you can select newsletter from table where employee's user id equal connected User.Identity.Id. for example
var news = context.Newsletters.Where(n=>n.Staff.UserId==User.Identity.Id);
var infos = context.StaffInfos.Where(si=>si.Staff.UserId==User.Identity.Id);
in case the tables are not in relationship, you'll need to do like following
var employee = context.Staffs.FirstOrDefault(s => s.UserId == User.Identity.Id);
if(employee != null) {
var infos = context.StaffInfos.Where(si=>si.IdEmployee==employee.EmployeeId);
return View(infos);
} else {
return Content("You don't have an account associated to your staff info...");
}
Please replace fields in these queries with the names of your fields.
I have 2 DB in firebase: USERS and GROUPS as structured below:
USERS
USERID
USERNAME
USEREMAIL
USERPHONE
GROUPS
GROUPID
GROUPNAME
GROUPPIC
TIMESTAMP
I logged in as user123. In the GroupsViewController, all the groups will be displayed and I have the option to block/unblock certain group(s). Once it is blocked, that particular group shouldn't be displayed in my GroupsViewController.
Now, I wanted to restructure the DB so that the blocked group will not display for that particular user (user123). I'm not sure whether to have child node BLOCKEDUSERS under GROUPS or have child node BLOCKEDGROUPS under USERS, like the below ones.
Which one is best and why? And how to show all the groups except the blocked ones
USERS
USERID
USERNAME
USEREMAIL
USERPHONE
BLOCKEDGROUPS
GROUPID
GROUPS
GROUPID
GROUPNAME
GROUPPIC
TIMESTAMP
BLOCKEDUSERS
USERID
I really appreciate your help.
Update :
I have added blockedGroups under userInfo, added some groupID to specific user.uid and created rules as you can see below. Ideally "groupID2" group should be blocked for the user "6viyeuDpC5TPbO3coElZp3LWWdH3", but the permission is denied for all the users. What I'm doing wrong?
You can add another DB which tracks user based permission. It should maintain all the user-specific permissions, As you are already maintaining the group id, set it as Enable/Disable the flag.
Follow instruction below
create DB user_group_permission.
Add fields user_id, group_id and status
Make status by default 1(consider it as not blocked).
When you want to block change the status to 0(consider it as not blocked).
You have to handle this in the code with an enum to make more sense to the value.
The advantage of this module is that later if you add other features other than just blocked and unblock, you will just have to increment status value to 2,3,4 and so on and update the enum.
You can use both structure options together if you wanted to start fanning out your data.
Using the first option of having a list of blockedGroups under the user node makes it easy to deny access to groups that the user has blocked by using Firebase security rules:
{
"rules": {
"groups": {
"$groupId": {
".read": "root.child('users').child(auth.uid)
.child('blockedGroups').child($groupId)
.exists() != true"
}
}
}
}
So your database structure would become something similar to:
users
|- userId1
|- userName: "User 1"
|- ...
|- blockedGroups
|- groupId1: true
groups
|- groupId1
|- groupName: "Group 1"
|- ...
|- groupId2
|- name: "Group 2"
|- ...
Where each user ID is a user's UID from FirebaseAuth#getUid() which will match the currently logged in user to auth.uid in the rules. And the list of blockedGroups is an index list of group IDs, like in the flatten data structures documentation example.
Unfortunately, the security rule method above won't work as a way to filter the groups list, but only as a method to deny access to specific group nodes. This is because rules are applied atomically top-down.
Instead, you'll need to retrieve the list of blockedGroups for the user, and retrieve all the groups, then do a client-side filter to ignore or discard the groups that the user doesn't want to see. Something like this in Swift:
var blockedGroups = [String]()
blockedGroupsRef.observe(.value) { snapshot in
for child in snapshot.children {
blockedGroups.append(child.key)
}
updateView()
}
func updateView() {
groupsRef.observe(.value) { snapshot in
for child in snapshot.children {
if blockedGroups.contains(child.key) == false {
// Add to view
}
}
}
}
Alternatively, depending on the requirements for your app, if you don't want to perform client-side filtering, you could flip the group management to be opt-in rather than opt-out, and use a list of subscribedGroups instead and then loop through this list to retrieve each group from the database individually by key.
I have a Users table, along with Roles table with many to many relation
Users
Id, Name, Password, ICollection<Role> ....
Roles
Id, Name
and UsersRoles
UserId, RoleId
Now if I retrieve a user object from the database and modify the Password field of example, an exception will occur if i don't load the roles even though I'm not touching the roles of the user.
So the question is: how can I update the Users table without retrieving all the roles every time?
Here is the situation: a user table contains data that overlap with other users' data such as country, age group, gender, etc.
In MySQL, I would use three tables or two. In the case storing country, I would use three tables: one would the be user table, the other country table, and the last one that joins the two. (Of course I could use two tables if one user has one country.)
ex) user(id, otherUserInfo),
userCountry(id_user, id_country),
country(id, countryName)
Being new to Grails, I was wondering what's the best way to represent this schema. Is it necessary to create 2-3 domain classes? Or is there some magical feature (like so many other Grails features (: ) to do this in a single domain class.
More than likely you could do it with two.
class User {
//other user info
//if user only has one country
Country country
//if user has many countries
static hasMany = [countries: Country]
}
class Country {
//country info
}
Since you probably don't want a Country to belong to the User, you don't need anything in the Country class to indicate ties back to the User. Grails/Gorm will handle creating the linking entities in either of these cases.
If the user has many countries and you needed to store other information regarding the relationship you would need to create your own join table. For example if you needed to keep the number of visits the user has made to the country:
class User {
//other user info
static hasMany = [userCountries: UserCountry]
}
class UserCountry {
static belongsTo = [user: User]
Country country
int numberOfVisits
}
class Country {
//country info
}