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.
Related
I'm using Cognito User Pool for my iOS App User Registration. In general, when Registering a user with Cognito I'm using the email as userID. And also I'm collecting other info like Phone number, Business Name and etc. In this case when I try to register with the same email id with a Different Business name it will show an alert like User already Exist.
In my new Work case, I want to save/register the same email with a different Business name. How can I achieve it?
for example, if we are using a DynamoDB table we have the Partition key and Sort key. By using those we set the email as the Partition key and the Business Name as the Sort key and we can achieve uniqueness.
can we implement the same using Cognito? Does Cognito support the Partition key and Sort key concept?
Is there any way to achieve this by using Cognito?
Please help me with this issue.
Let start from this link :
Configuring User Pool Attributes
You can have changeable standard attributes as far as they are not required. You can add custom attributes as well but they are un-changeable.
Well, let's move on another case on some projects I have a need to storing a user federated identity id (i.e ap-northeast-1:3c2f5c30-0dc8-4d74-91a8-bf5c688abcde) into a cognito user pool attribute. I should store it on a custom attribute (i.e custom:identity_id) because of it will never change in the future.
Back to your case, as it will be dynamic values where users has ability to change their organization list so you can utilize an unused standard attributes for. For example, I will use "zoneinfo" although it looks strange to use unassociated attribute because there is no one with the name "organization". However at least users can pull their organization from their token once logged-in as like :
"zoneinfo": "[org1, org2, org3, etc]"
But it can't full accomodate your case as it should be stored after user registration. While if you set the "zoneinfo" on required registration, it must be unchangeable then. To solve this problem, you can utilize the cognito user pool Post-Confirmation trigger to run some logic to init a standard attribute with empty organization list (i.e "zoneinfo": "[]") adminUpdateUserAttributes. That so users can modify this attribute then because of it is not required attribute.
Sample of adminUpdateUserAttributes :
async function updateOrgAttr() {
try {
var params = {
UserAttributes: [ /* required */
{
Name: 'zoneinfo', /* required */
Value: '[]'
}
],
UserPoolId: 'ap-northeast-1_xxxxxxxx', /* required */
Username: event.userName /* event.userName is an item of post-confirmation trigger event source */
};
let cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
await cognitoidentityserviceprovider.adminUpdateUserAttributes(params).promise()
} catch(e) {
throw e
}
}
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.
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.
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 am currently developing a plugin that allows project administrators to manage users in groups. I have been combing through the api reference documentation and I cannot seem to find any calls that I can make that will allow me to see the groups associated with a particular project.
I have looked through the API's at every location that is relevant to what I am searching for to no avail.
I currently have a database query that provides me what I am looking for.
SELECT ROLETYPEPARAMETER AS "Groups"
FROM projectrole PROJECT_ROLE,
projectroleactor PROJECT_ROLE_ACTOR
JOIN project PROJECT
ON PROJECT.id = PROJECT_ROLE_ACTOR.PID
JOIN cwd_group
ON group_name = roletypeparameter
WHERE PROJECT_ROLE_ACTOR.projectroleid = PROJECT_ROLE.id
AND PKEY = <projectkey>;
I would prefer to manipulate this data through the API if at all possible.
All the other pieces are available for me to complete the plugin to add, remove users from groups.
I know the information that I am looking for is available. If you navigate to the roles page you have both the users in roles, and also the groups in roles. I'm sure i'm overlooking something minor with the API to give me the groups associated with the project.
After implementing my database route, I came back to a non-database approach. This is the implementation that solves the problem.
The way the groups are implemented is as a role actor as a set underneath the project roles. One more level down you have your group names as a descriptor to the role actor.
//Create TreeMap to Store the Role-Group association. Note a role can have more than one group
TreeMap<String,Collection<String>> projectGroups = new TreeMap<String,Collection<String>>();
//Get all the project roles
Collection<ProjectRole> projectRoles = projectRoleManager.getProjectRoles();
//Iterate through each role and get the groups associated with the role
for (ProjectRole projectRole : projectRoles)
{
//Get the role actors for the project role
ProjectRoleActors roleActors = projectRoleManager.getProjectRoleActors(projectRole, project);
//Create an iterator to grab all of the groups for this project role
Iterator <RoleActor> roleActorIterator = roleActors.getRoleActors().iterator();
//Create a collection of strings to store all of the group's roles to put in the map
Collection <String> groupRoles = new ArrayList<String>();
//Iterate the role actors to get the groups
while (roleActorIterator.hasNext())
{
//Add the group by string name into collection
groupRoles.add(roleActorIterator.next().getDescriptor());
}//END While
//Add that role, and the associated groups to that role into our map.
projectGroups.put(projectRole.getName(), groupRoles);
}//END For
The output of this will look similarly to this
{Administrators=[jira-administrators], Developers=[jira-developers, jira-users], Users=[jira-users]}