How to manage fine grain permissions in Elasticsearch? - permissions

I need to store in a consistent way the role/groups that can access the information but I'm not sure what's the best way to do it.
Summary: I have 2 kinds of docs "tweet" and "blog":
At tweet level, I store the group name allowed to access the information
blog is more complex, there are metadata (title, description, nature, ...) but some of those informations can be restricted to some groups of user (only admin, or logged_in users)
What the best way to map this with Elasticsearch ?
As of today, I end up with documents like:
/tweet/455
{
id: 112,
ugroups: [ "restricted_user", "admin" ],
description: "foo",
},
{
id: 113,
ugroups: [ "anonymous" ]
description: "foo",
}
and
/blog/500
{
id: 5,
fields: [
{
"nature": {
"value": "foo",
"ugroup": [ "admin" ]
}
}
]
}
{
id: 6,
fields: [
{
"comment": {
"value": "foo",
"ugroup": [ "anonymous" ]
}
}
]
}
When user want to search in tweet, that's easy, I build a term query with words submitted by the user and I append the groups the user belongs to this query.
But how to make a query that will take this "ugroup" thing at various level ?
Ideally I could issue a query like:
search in tweet with tweet.ugroup: "anonymous" and in blog with blog.fields.*.ugroup: "anonymous"
Is there a way to write such a query ?

Related

Filtering items by relation using knex query

I'm trying to filter items by relation using a knex query. I'm almost there (I think) but struggling a little and could use some help as this is new to me.
I have a list of users who are following people and have followers. I'm trying to return a list of users who I'm not already following. Below is my code so far:
const users = await knex("users-permissions_user").whereNotExists(
function () {
this.select("*")
.from("users_followers__users_followings")
.where("user_id", "users-permissions_user.id")
.where("follower_id", id);
}
);
This returns a list of users who currently have no followers and users where I'm the only follower. Any users who I follow and also have more followers are still returned. I thought like would achieve this type of filter but I must be doing it wrong.
Here is how the table for the followers/following relation appears in my db:
And here is the data that would be returned from the above query:
[
{
"id": "138",
"followers": [
{
"id": "143"
}
]
},
{
"id": "140",
"followers": [
{
"id": "160"
},
{
"id": "136"
}
]
},
{
"id": "135",
"followers": []
},
{
"id": "136",
"followers": []
}
]
As you can see, users with no followers are returned as are users who I'm not already following but users who have multiple followers, including me (ID 160), are returned when they should be omitted.
Any advice would be greatly appreciated!
So the reason that users like 140 are being returned even though they are following you (160) is because they are following at least one other person who isn't you, which means your where clause will match them.
If you want to return only users who are not following you, you could achieve this by replacing your left join and where clause with a where not exists clause. In knex that would look something like:
qb.whereNotExists(function() {
this.select(1)
.from('users_followers__users_followings')
.where('users_followers__users_followings.user_id', knex.ref('users-permissions_user.id'))
.where('users_followers__users_followings.follower_id', id);
});

How not to expose duplicated (normalize?) nodes via GraphQL?

Given "user has many links" (what means a link was created by a user) DB entities relations, I want to develop API to fetch links along with users so that the returned data does not contain duplicated users.
In other words, instead of this request:
query {
links {
id
user {
id email
}
}
}
that returns the following data:
{
"data": {
"links": [
{
"id": 1,
"user": {
"id": 2,
"email": "user2#example.com"
}
},
{
"id": 2,
"user": {
"id": 2,
"email": "user2#example.com"
}
}
]
}
}
I want to make a request like this (note the "references" column):
query {
links {
id
userId
}
references {
users {
id
email
}
}
}
that returns associated users without duplicates:
{
"data": {
"links": [
{
"id": 1,
"userId": 2
},
{
"id": 2,
"userId": 2
},
],
"references": {
"users": [
{
"id": 2,
"email": "user2#example.com"
}
]
}
}
}
That should reduce amount of data transferred between client and server that adds a bit of speed boost.
Is there ready common implementation on any language of that idea? (Ideally, seeking for Ruby)
It's not a query or server role to normalize data.
there are no such possibilities in GraphQL specs;
server must return all asked fields within queried [response] structure;
... but you can implement some:
standarized (commonly used) pagination (relay style edges/nodes, nodes only or better both);
query [complexity] weights to promote this optimized querying style - separate problem;
reference dictionary field within queried type;
links {
egdes {
node {
id
title
url
authorId
# possible but limited usage with heavy weights
# author {
# id
# email
# }
}
}
pageInfo {
hasNextPages
}
referencedUsers {
id
email
}
}
where:
User has id and email props;
referencedUsers is [User!] type;
node.author is User type;
Normalizing Graphql client, like Apollo, can easily access cached user fields without making separate requests.
You can render (react?) some <User/> component (within <Link /> component) passing node.authorId as an argument like <User id={authorId} />. User component can useQuery hook with cache-only policy to read user props/fields.
See Apollo docs for details. You should implement this for yourself and document this to help/guide API users.

how to select a single item and get it's relations in faunadb?

I have two collections which have the data in the following format
{
"ref": Ref(Collection("Leads"), "267824207030650373"),
"ts": 1591675917565000,
"data": {
"notes": "voicemail ",
"source": "key-name",
"name": "Glenn"
}
}
{
"ref": Ref(Collection("Sources"), "266777079541924357"),
"ts": 1590677298970000,
"data": {
"key": "key-name",
"value": "Google Ads"
}
}
I want to be able to query the Leads collection and be able to retrieve the corresponding Sources document in a single query
I came up with the following query to try and use an index but I couldn't get it to run
Let(
{
data: Get(Ref(Collection('Leads'), '267824207030650373'))
},
{
data: Select(['data'],Var('data')),
source: q.Lambda('data',
Match(Index('LeadSourceByKey'), Get(Select(['source'], Var('data') )) )
)
}
)
Is there an easy way to retrieve the Sources document ?
What you are looking for is the following query which I broke down for you in multiple steps:
Let(
{
// Get the Lead document
lead: Get(Ref(Collection("Leads"), "269038063157510661")),
// Get the source key out of the lead document
sourceKey: Select(["data", "source"], Var("lead")),
// use the index to get the values via match
sourceValues: Paginate(Match(Index("LeadSourceValuesByKey"), Var("sourceKey")))
},
{
lead: Var("lead"),
sourceValues: Var("sourceValues")
}
)
The result is:
{
lead: {
ref: Ref(Collection("Leads"), "269038063157510661"),
ts: 1592833540970000,
data: {
notes: "voicemail ",
source: "key-name",
name: "Glenn"
}
},
sourceValues: {
data: [["key-name", "Google Ads"]]
}
}
sourceValues is an array since you specified in your index that there will be two items returned, the key and the value and an index always returns the array. Since your Match could have returned multiple values in case it wasn't a one-to-one, this becomes an array of an array.
This is only one approach, you could also make the index return a reference and Map/Get to get the actual document as explained on the forum.
However, I assume you asked the same question here. Although I applaud asking questions on stackoverflow vs slack or even our own forum, please do not just post the same question everywhere without linking to the others. This makes many people spend a lot of time while the question is already answered elsewhere.
You might probably change the Leads document and put the Ref to Sources document in source:
{
"ref": Ref(Collection("Leads"), "267824207030650373"),
"ts": 1591675917565000,
"data": {
"notes": "voicemail ",
"source": Ref(Collection("Sources"), "266777079541924357"),
"name": "Glenn"
}
}
{
"ref": Ref(Collection("Sources"), "266777079541924357"),
"ts": 1590677298970000,
"data": {
"key": "key-name",
"value": "Google Ads"
}
}
And then query this way:
Let(
{
lead: Select(['data'],Get(Ref(Collection('Leads'), '267824207030650373'))),
source:Select(['source'],Var('lead'))
},
{
data: Var('lead'),
source: Select(['data'],Get(Var('source')))
}
)

How to make a relationship of data from user pool to aws appsync model

I am new to amplify and appsync. I am trying to create a Post model that needs to have a relationship of the user who is creating the Post. The problem is my user is from cognito user pool.
What I want is the user from cognito I don't want to create new user table on dynamo db because cognito user pool already has its information I just want to get the user info that is creating that Post if I query the post.
How should I create a relationship of this?
I create amlify api like this
? Please select from one of the below mentioned services: GraphQL
? Choose the default authorization type for the API API key
? Enter a description for the API key:
? After how many days from now the API key should expire (1-365): 7
? Do you want to configure advanced settings for the GraphQL API Yes, I want to make some additional changes.
? Configure additional auth types? Yes
? Choose the additional authorization types you want to configure for the API Amazon Cognito User Pool
Cognito UserPool configuration
Use a Cognito user pool configured as a part of this project.
? Configure conflict detection? No
Here is my current schema.grapql
type Post
#model
#versioned
#aws_cognito_user_pools
#auth(rules: [{ allow: owner, queries: null }, { allow: public }]) {
id: ID!
title: String!
content: String
thumb: String
slug: String!
allow_comments: Boolean
owner: String!
post_type: String!
add_to_nav: Boolean!
version: Int!
comments: [Comment] #connection(name: "PostComments")
}
type Comment
#model
#versioned
#aws_cognito_user_pools
#auth(rules: [{ allow: owner, queries: null }]) {
id: ID!
content: String
version: Int!
post: Post #connection(name: "PostComments")
}
====================================================
EDIT: Added result of data that I want
Here is the query that I want to perform
query ListPost {
listPosts {
items {
title
content
owner{
username
id
email
first_name
last_name
}
}
}
}
}
Result that I want
{
"data": {
"listPosts": {
"items": [
{
"title": "title 1"
"content": "Test long text cotent"
"owner": {
"username": "user1"
"id": "234234234"
"email": "user1#test.com"
"first_name": "John"
"last_name": "Doe"
}
},
{
"title": "title 1"
"content": "Test long text cotent"
"owner": {
"username": "user1"
"id": "234234234"
"email": "user1#test.com"
"first_name": "John"
"last_name": "Doe"
}
},
]
}
}
}
I can't find any documentation how to build something like this.
This should help for anyone else that is looking into doing this:
https://docs.amplify.aws/cli-legacy/graphql-transformer/function/#usage
Scroll to:
Example: Get the logged in user from Amazon Cognito User Pools

express-graphql: How to remove external "data" object layer.

I am replacing an existing REST endpoint with GraphQL.
In our existing REST endpoint, we return a JSON array.
[{
"id": "ABC"
},
{
"id": "123"
},
{
"id": "xyz"
},
{
"id": "789"
}
]
GraphQL seems to be wrapping the array in two additional object layers. Is there any way to remove the "data" and "Client" layers?
Response data:
{
"data": {
"Client": [
{
"id": "ABC"
},
{
"id": "123"
},
{
"id": "xyz"
},
{
"id": "789"
}
]
}
}
My query:
{
Client(accountId: "5417727750494381532d735a") {
id
}
}
No. That was the whole purpose of GraphQL. To have a single endoint and allow users to fetch different type/granularity of data by specifying the input in a query format as opposed to REST APIs and then map them onto the returned JSON output.
'data' acts as a parent/root level container for different entities that you have queried. Without these keys in the returned JSON data, there won't be any way to segregate the corresponding data. e.g.
Your above query can be modified to include another entity like Owner,
{
Client(accountId: "5417727750494381532d735a") {
id
}
Owner {
id
}
}
In which case, the output will be something like
{
"data": {
"Client": [
...
],
"Owner": [
...
]
}
}
Without the 'Client' and 'Owner' keys in the JSON outout, there is no way to separate the corresponding array values.
In your case, you can get only the array by doing data.Client on the returned output.