Is there a way to setup a field-level authorisation on FaunaDB + GraphQL? - nosql

I'm having troubles finding a way to hide user emails from everyone, except the owner (user has access to only his email). Is there a way to hide a certain document field, for a certain roles?
Here is an example I found that creates a role with dynamic access to the whole User collection:
CreateRole({
name: "tier1_role",
membership: {
resource: Collection("User"),
predicate: Query(
Lambda("userRef",
// User attribute based rule:
// It grants access only if the User has TIER1 role.
// If so, further rules specified in the privileges
// section are applied next.
Equals(Select(["data", "role"], Get(Var("userRef"))), "TIER1")
)
)
},
privileges: [
{
// Note: 'allUsers' Index is used to retrieve the
// documents from the File collection. Therefore,
// read access to the Index is required here as well.
resource: Index("allUsers"),
actions: { read: true }
}
]
})
I tried to change it a bit, but I wasn't able to set up field-level access.
Let's say I'd set up FaunaDB with GraphQL schema below.
enum UserRole {
TIER1
}
type User {
email: String! #unique
username: String! #unique
role: UserRole!
}
type Query {
allUsers: [User!]
}
type Mutation {
addUsers(new_users: [UserInput]): [User]
#resolver(name: "add_users", paginated: false)
}
How do create a FaunaDB role in such a way that all of the users (except the current one) in resulting array from allUsers query, will not have email field?
I could break User collection into two: one is public, the other is accessible to a document owner, but this sounds wrong.
I'm new to the noSQL concept, so maybe I'm looking at this problem from the wrong perspective?

it's a request that came up a few times. You probably want to do this straight in FaunaDB's ABAC role system but although it provides row-level security, hiding a specific field is currently not provided yet. The feedback has been logged though, we will look into it.
The current way to do this is to split out Users from Accounts and fetch Users instead of Accounts. It would be useful to have something like hidden fields though in the future.
If you think of it, in this case, it does make sense to split authentication information from User information. You never know that you might offer another way to authentication in the future. I still recall from the Phoenix Framework book that they do it there was well and considered it a good practice.
You could also make a thin wrapper using Apollo in a serverless function and filter out these fields when you pass through the results. There is a guide that explains how to build such a thin Apollo middleware that just delegates to FaunaDB https://www.gatlin.io/blog/post/social-login-with-faunadb-and-auth0

Related

Is there a way to create public/private fields for a mongodb schema in golang?

I'm creating a backend for a moderately large-scale application, and I came across a difficulty with restraining what fields users can access. For instance, a user should not be able to modify their follower count with a PUT request to an update endpoint, yet the only way to really remove the followerCount field from the golang struct representing the user schema is by creating an entirely new schema for updates in particular. I've been doing this, and my backend code base is way more complex than it needs to be, to the point where it's nearly unmanageable.
Here's an example of the schemas I have:
type User struct {
ID primitive.ObjectID `bson:"_id" json:"_id"`
// CHANGEABLE
Username string `bson:"username" json:"username"`
Email string `bson:"email" json:"email"`
Password string `bson:"password" json:"password"`
Archived bool `bson:"archived" json:"archived"`
// UNCHANGEABLE
Sessions []primitive.ObjectID `bson:"sessions" json:"sessions"`
IsCreator bool `bson:"isCreator" json:"isCreator"`
FollowerCount int `bson:"followerCount" json:"followerCount"`
}
and for updates specifically
type UserUpdate struct {
Username string `bson:"username,omitempty" json:"username,omitempty"`
Email string `bson:"email,omitempty" json:"email,omitempty"`
Password string `bson:"password,omitempty" json:"password,omitempty"`
Archived bool `bson:"archived,omitempty" json:"archived,omitempty"`
}
Is there a way to make public/private fields within a mongo schema so I can simplify this process? And if not, can you advise me on a better solution? Nothing is coming to mind for me.
I've continued creating new "sub-schemas" built off the same schema for specific purposes (i.e. Creation, Updating, Getting, etc.). Changing one field name takes nearly 30 minutes to change across schemas, which is not ideal.

Field visibility restriction/access control in MongoDB

I currently own a MongoDB database that contains a collection named User. User collection has a field called contact_info among several other fields.
My requirement is to allow a member in my team having access to the database with 'admin' role to view contact_info when querying a user, however, a team member with 'developer' role should be able to query a user but not view his/her contact_info (i.e. contact_info key in the user document should be hidden or masked for a team member with 'developer' role). I am looking for a field-level visibility restriction in MongoDB to comply with GDPR standards.
I am comparatively new to MongoDB and did some search for this requirement, but could not find any direct solution for this. Any help with be greatly appreciated.
Looks like you could create a view with only the fields needed (https://docs.mongodb.com/manual/core/views/).
Then I would create an extra user developer and 1 extra role developerRole
This developerRole should only have access to the view created.

Access Control Filtering of Query before its sent to Prisma server/DB

Right now I am using accesscontrol to manage the ACL and it is working great. It looks something like this:
const methods = {
async update(parent, { data }, ctx, info) {
const acUpdate = ac.can('role').updateOwn('model')
if (! acUpdate.granted) throw new ACError()
const filtered = acUpdate.filter({ ...data })
return await ctx.db.mutation.updateOrganization({
data: filtered,
where: { id }
}, info)
}
}
However, on a Query method from GraphQL I don't know how to filter the requests to the DB. For example, on a nested query it may look like this:
{
model {
id
name
user {
id
name
pictures {
id
name
}
}
}
}
So on the resolver it would check if they have access to Model, then it would send the request to the Prisma server without filtering the GQL schema. In this scenario let's say that the user has access to read model but not user. Ideally I'd like to do a permission.filter(...) on the actual request schema (info?) before sending it to Prisma. Have any of you solved this? Of course its possible to filter the request after it has resolved, but that level of computation is not necessary and can cause issues if abused.
I found out this is the topic I was addressing in one of my issue responses because I thought it was asked there. I recognize now that I must have confused it with this one being open in one of the tabs in the back.
https://github.com/maticzav/graphql-shield/issues/113#issuecomment-423766569
I think that the second part of my response concerns you the most. I hope you find it helpful! 🙂
I was having the exact same problem and i am now solving it by using prisma client for making the requests to prisma. Prisma client only queries one level deep each time so you get full control of the resolvers also in nested queries.
See https://stackoverflow.com/a/53703140/1391050

Authenticate a user based on a token and not through mysql table

round 2: trying to make this clearer:
I have two totally and completely separate services (both laravel 5.3).
One is an authentication service that has access to a user mysql database, permissions and role.
The other is a resource service. It couldn't care less about the user table and does not have access to any user table.
That means that any type of Auth::loginById()... would never work in this service because there is no user table.
I want to use JWT to have users access the resource service API, and have the user Auth to act as if a user is authenticated - but again - all I have are the claims inside the JWT - no user table. The claims include some information about the user - id, name, etc. I want to do something like this:
$userObj = JWTAuth::parseToken()->getPayload()->claims; // { id:4, name: Birdman, email: birdy414141#gmail.com}
Auth::login($userObj)
and then have access to the user object like usual
echo Auth::user()->name // Birdman
Has anyone tried anything like this?
I have found that doing this more or less works:
$u = new User();
$u->user_id = (JWTAuth::parseToken()->getPayload()->get('sub'));
$u->name = (JWTAuth::parseToken()->getPayload()->get('name'));
Obviously I can't $u->save() here because again - there is no user database.
I can do this though:
Auth::login($u);
and then I can later call Auth::user()->name properly...
I'm asking if I'm doing something exotic here or is this good stuff. Where will this fail?

Joining entities with #ManyToMany relationship

I have these entities:
User
Role
Permission
A user has many roles and a role has many permissions.
What is the best way to retrieve a set of permission a user has?
I need a method to check if a User has a particular Permission.
This is what I have so far:
public boolean hasPermission(String permissionString) {
if (!authenticated) return false;
for (Role role : user.getRoles()) {
for (Permission permission : role.getPermissions()) {
if (permission.getName().equals(permissionString)) {
return true;
}
}
}
return false;
}
A second, but related question -- where should I put the code that checks if a user has a particular permission?
In the User entity?
In the UserBean EJB?
In the Authentication JSF Managed Bean?
It depends on your mappings, the number of objects in the list, if the lists have already been fetched, your database connections, the database tuning etc.
You would have to try with production data to determine what ways are best.
For instance, If your collections have been prefetched with a join query, then traversing them in Java is trivial. If they haven't, each access in the for loop would cause a query to populate the objects. If it is the last one all the time, it means your java code causes you to traverse your object graph in the worst way possible and it would have been better to fetch it upfront. So you would be losing any benefit of lazy access, and would be better of hitting the database once to query for the permission linked to this user with the permissionString name: "Select p from u User join u.roles r join r.permissions p where p.name = :permissionName".
Only testing on production data will give you the best answer for your situation, and numerous other decisions in the application and mappings change the outcome.