From 2aef76f07c5c9bcaf124c81a0cad8c29313be024 Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Fri, 10 May 2024 11:05:17 -0400 Subject: [PATCH 1/5] Adding wildcard and excluded users --- docs/content/concepts.mdx | 9 ++- .../getting-started/perform-list-users.mdx | 79 ++++++++++++++++++- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/docs/content/concepts.mdx b/docs/content/concepts.mdx index 48490f321..ceba4812f 100644 --- a/docs/content/concepts.mdx +++ b/docs/content/concepts.mdx @@ -606,7 +606,7 @@ A **list users request** is a call to the diff --git a/docs/content/getting-started/perform-list-users.mdx b/docs/content/getting-started/perform-list-users.mdx index a68fb5bb2..2a3273a87 100644 --- a/docs/content/getting-started/perform-list-users.mdx +++ b/docs/content/getting-started/perform-list-users.mdx @@ -61,7 +61,7 @@ This section will illustrate how to perform a + + ## Related Sections Date: Fri, 10 May 2024 12:40:36 -0400 Subject: [PATCH 2/5] Adding usersets section, minor fixes --- .../getting-started/perform-list-users.mdx | 72 ++++++++- .../SnippetViewer/ListUsersRequestViewer.tsx | 152 ++++++++---------- 2 files changed, 138 insertions(+), 86 deletions(-) diff --git a/docs/content/getting-started/perform-list-users.mdx b/docs/content/getting-started/perform-list-users.mdx index 2a3273a87..aabcf7d60 100644 --- a/docs/content/getting-started/perform-list-users.mdx +++ b/docs/content/getting-started/perform-list-users.mdx @@ -28,7 +28,7 @@ ListUsers is currently in an experimental release. Read [the announcement](https -This section will illustrate how to perform a request to determine all the of a given that have a specified with a given . +This section will illustrate how to perform a request to determine all the of a given that have a specified with a given . ## Before You Start @@ -175,6 +175,72 @@ The result `user:anne` and `user:beth` are the `user` objects that have the `rea The performance characteristics of the ListUsers endpoint vary drastically depending on the model complexity, number of tuples, and the relations it needs to evaluate. Relations with 'and' or 'but not' are particularly expensive to evaluate. ::: +## Usersets + +In the above example, only specific subjects of the `user` type were returned. However, groups of users, known as [usersets](./modeling/building-blocks/usersets.mdx), can also be returned from the List Users API. This is done by specifying a `relation` field in the `user_filters` request object. Usersets will only expand to the underlying subjects if that `type` is specified as the user filter object. + +Below is an example where usersets can be returned: + +```dsl.openfga +model + schema 1.1 + +type user + +type group + relations + define member: [ user ] + +type document + relations + define viewer: [ group#member ] +``` + +With the tuples: + +| user | relation| object| +|------|---------|-------| +| group:engineering#member | viewer | document:1| +| group:product#member | viewer | document:1| +| user:will | member | group:engineering#member| + +Then calling the List Users API for `document:1` with relation `viewer` of type `group#member` will yield the below response. Note that the `user:will` is not returned, despite being a member of `group:engineering#member` because the `user_filters` does not target the `user` type. + + + + ## Type-bound Public Access The list users API supports tuples expressing public access via the wildcard syntax (e.g. `user:*`). Wildcard tuples that satisfy the query criteria will be returned with the `wildcard` root object property that will specify the type. The API will not expand wildcard results further to any ID'd subjects. Further, specific users that have been granted accesss will be returned in addition to any public acccess for that user's type. @@ -226,10 +292,10 @@ With the tuples: | user:* | viewer| document:1| | user:anne | blocked| document:1| -And ultimately calling the List Users API for `document:1` with relation `viewer` of type `user`: +Calling the List Users API for `document:1` with relation `viewer` of type `user` will yield the response below. It indicates that any object of type `user` (including those not already in OpenFGA as parts of tuples) has access to the system, except for a `user` with id `anne`. ` --contextual-tuple "${tuple.user} ${tuple.relation} ${tuple.object}"`) - .join(' ')}` - : '' - }${context ? ` --context='${JSON.stringify(context)}'` : ''} + return `fga query list-users --store-id=\${FGA_STORE_ID} --model-id=${modelId} --object ${objectType}:${objectId} --relation ${relation} --user-filter ${userFilterType}${userFilterRelation ? `#${userFilterRelation}` : ''} ${contextualTuples + ? `${contextualTuples + .map((tuple) => ` --contextual-tuple "${tuple.user} ${tuple.relation} ${tuple.object}"`) + .join(' ')}` + : '' + }${context ? ` --context='${JSON.stringify(context)}'` : ''} # Response: ${response}`; case SupportedLanguage.CURL: @@ -61,17 +60,15 @@ function listUsersRequestViewer(lang: SupportedLanguage, opts: ListUsersRequestV "relation": "${relation}", "user_filters": [ { - "type": "${userFilterType}"${ - userFilterRelation - ? `, + "type": "${userFilterType}"${userFilterRelation + ? `, , "relation": "${userFilterRelation}"` - : '' - } + : '' + } } - ]${ - contextualTuples - ? `, + ]${contextualTuples + ? `, "contextual_tuples": { "tuple_keys": [${contextualTuples .map( @@ -81,12 +78,11 @@ function listUsersRequestViewer(lang: SupportedLanguage, opts: ListUsersRequestV .join(',')} ] }` - : '' - }${ - context - ? `, + : '' + }${context + ? `, "context":${JSON.stringify(context)}` - : '' + : '' } }' @@ -100,39 +96,36 @@ function listUsersRequestViewer(lang: SupportedLanguage, opts: ListUsersRequestV id: "${objectId}" }, user_filters: [{ - type: "${userFilterType}",${ - userFilterRelation - ? `, + type: "${userFilterType}",${userFilterRelation + ? `, relation: "${userFilterRelation}"` - : '' - } + : '' + } }], - relation: "${relation}",${ - contextualTuples?.length - ? ` + relation: "${relation}",${contextualTuples?.length + ? ` contextualTuples: { tuple_keys: [${contextualTuples - .map( - (tupleKey) => `{ + .map( + (tupleKey) => `{ user: "${tupleKey.user}", relation: "${tupleKey.relation}", object: "${tupleKey.object}" }`, - ) - .join(', ')}] + ) + .join(', ')}] },` - : '' - }${ - context - ? ` + : '' + }${context + ? ` context:${JSON.stringify(context)},` - : '' - } + : '' + } }, { authorization_model_id: "${modelId}", }); // response.users = [${expectedResults.users.map((u) => JSON.stringify(u)).join(',')}] -// response.excluded_users = p${expectedResults.excluded_users.map((u) => JSON.stringify(u)).join(',')}]`; +// response.excluded_users = [${expectedResults.excluded_users.map((u) => JSON.stringify(u)).join(',')}]`; case SupportedLanguage.GO_SDK: /* eslint-disable no-tabs */ return `options := ClientListUsersOptions{ @@ -147,32 +140,29 @@ body := ClientListUsersRequest{ Id: "${objectId}", }, Relation: "${relation}", - UserFilters: userFilters,${ - !contextualTuples - ? '' - : ` + UserFilters: userFilters,${!contextualTuples + ? '' + : ` ContextualTuples: []ClientContextualTupleKey{ -${ - !contextualTuples - ? '' - : contextualTuples - .map( - (tuple) => - ` { +${!contextualTuples + ? '' + : contextualTuples + .map( + (tuple) => + ` { User: "${tuple.user}", Relation: "${tuple.relation}", Object: "${tuple.object}", },`, - ) - .join('\n') -} + ) + .join('\n') + } },` - }${ - context - ? ` + }${context + ? ` Context: &map[string]interface{}${JSON.stringify(context)},` - : '' - } + : '' + } } data, err := fgaClient.ListUsers(context.Background()). @@ -195,31 +185,28 @@ var body = new ClientListUsersRequest { Relation = "${relation}", UserFilters = new List { new() { - Type = "${userFilterType}"${ - userFilterRelation - ? ` + Type = "${userFilterType}"${userFilterRelation + ? ` Relation = "${userFilterRelation}" ` - : '' + : '' } } - }${ - contextualTuples - ? `, + }${contextualTuples + ? `, ,ContextualTuples = new List({ ${contextualTuples - .map((tuple) => `new(user: "${tuple.user}", relation: "${tuple.relation}", _object: "${tuple.object}")`) - .join(',\n ')} + .map((tuple) => `new(user: "${tuple.user}", relation: "${tuple.relation}", _object: "${tuple.object}")`) + .join(',\n ')} })` - : '' - } - ${ - context - ? `Context = new { ${Object.entries(context) + : '' + } + ${context + ? `Context = new { ${Object.entries(context) .map(([k, v]) => `${k}="${v}"`) .join(',')} }` - : '' - } + : '' + } }; var response = await fgaClient.ListUsers(body, options); @@ -272,16 +259,15 @@ var response = await fgaClient.ListUsers(body, options); "${objectId}", // list the objects that the user \`${objectId}\` "${relation}", // has an \`${relation}\` relation "${objectType}", // and that are of type \`${objectType}\` - authorization_model_id = "${modelId}", // for this particular authorization model id ${ - contextualTuples - ? ` + authorization_model_id = "${modelId}", // for this particular authorization model id ${contextualTuples + ? ` contextual_tuples = [ // Assuming the following is true ${contextualTuples - .map((tuple) => `{user = "${tuple.user}", relation = "${tuple.relation}", object = "${tuple.object}"}`) - .join(',\n ')} + .map((tuple) => `{user = "${tuple.user}", relation = "${tuple.relation}", object = "${tuple.object}"}`) + .join(',\n ')} ]` - : '' - } + : '' + } ); Reply: ${response}`; @@ -291,13 +277,13 @@ Reply: ${response}`; ? ` .contextualTupleKeys( List.of(${contextualTuples.map( - (tuple) => ` + (tuple) => ` new ClientTupleKey() .user("${tuple.user}") .relation("${tuple.relation}") ._object("${tuple.object}") ))`, - )}` + )}` : ''; const contextCall = context ? ` From 9e5306ffa403636de6aee995ee252b259b20fd14 Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Fri, 10 May 2024 12:59:49 -0400 Subject: [PATCH 3/5] Fixing formatting --- .../SnippetViewer/ListUsersRequestViewer.tsx | 150 ++++++++++-------- 1 file changed, 82 insertions(+), 68 deletions(-) diff --git a/src/components/Docs/SnippetViewer/ListUsersRequestViewer.tsx b/src/components/Docs/SnippetViewer/ListUsersRequestViewer.tsx index 501473b0c..68f372a2b 100644 --- a/src/components/Docs/SnippetViewer/ListUsersRequestViewer.tsx +++ b/src/components/Docs/SnippetViewer/ListUsersRequestViewer.tsx @@ -38,12 +38,13 @@ function listUsersRequestViewer(lang: SupportedLanguage, opts: ListUsersRequestV case SupportedLanguage.PLAYGROUND: return `# Note: List Users is not currently supported on the playground`; case SupportedLanguage.CLI: - return `fga query list-users --store-id=\${FGA_STORE_ID} --model-id=${modelId} --object ${objectType}:${objectId} --relation ${relation} --user-filter ${userFilterType}${userFilterRelation ? `#${userFilterRelation}` : ''} ${contextualTuples - ? `${contextualTuples - .map((tuple) => ` --contextual-tuple "${tuple.user} ${tuple.relation} ${tuple.object}"`) - .join(' ')}` - : '' - }${context ? ` --context='${JSON.stringify(context)}'` : ''} + return `fga query list-users --store-id=\${FGA_STORE_ID} --model-id=${modelId} --object ${objectType}:${objectId} --relation ${relation} --user-filter ${userFilterType}${userFilterRelation ? `#${userFilterRelation}` : ''} ${ + contextualTuples + ? `${contextualTuples + .map((tuple) => ` --contextual-tuple "${tuple.user} ${tuple.relation} ${tuple.object}"`) + .join(' ')}` + : '' + }${context ? ` --context='${JSON.stringify(context)}'` : ''} # Response: ${response}`; case SupportedLanguage.CURL: @@ -60,15 +61,17 @@ function listUsersRequestViewer(lang: SupportedLanguage, opts: ListUsersRequestV "relation": "${relation}", "user_filters": [ { - "type": "${userFilterType}"${userFilterRelation - ? `, + "type": "${userFilterType}"${ + userFilterRelation + ? `, , "relation": "${userFilterRelation}"` - : '' - } + : '' + } } - ]${contextualTuples - ? `, + ]${ + contextualTuples + ? `, "contextual_tuples": { "tuple_keys": [${contextualTuples .map( @@ -78,11 +81,12 @@ function listUsersRequestViewer(lang: SupportedLanguage, opts: ListUsersRequestV .join(',')} ] }` - : '' - }${context - ? `, + : '' + }${ + context + ? `, "context":${JSON.stringify(context)}` - : '' + : '' } }' @@ -96,31 +100,34 @@ function listUsersRequestViewer(lang: SupportedLanguage, opts: ListUsersRequestV id: "${objectId}" }, user_filters: [{ - type: "${userFilterType}",${userFilterRelation - ? `, + type: "${userFilterType}",${ + userFilterRelation + ? `, relation: "${userFilterRelation}"` - : '' - } + : '' + } }], - relation: "${relation}",${contextualTuples?.length - ? ` + relation: "${relation}",${ + contextualTuples?.length + ? ` contextualTuples: { tuple_keys: [${contextualTuples - .map( - (tupleKey) => `{ + .map( + (tupleKey) => `{ user: "${tupleKey.user}", relation: "${tupleKey.relation}", object: "${tupleKey.object}" }`, - ) - .join(', ')}] + ) + .join(', ')}] },` - : '' - }${context - ? ` + : '' + }${ + context + ? ` context:${JSON.stringify(context)},` - : '' - } + : '' + } }, { authorization_model_id: "${modelId}", }); @@ -140,29 +147,32 @@ body := ClientListUsersRequest{ Id: "${objectId}", }, Relation: "${relation}", - UserFilters: userFilters,${!contextualTuples - ? '' - : ` + UserFilters: userFilters,${ + !contextualTuples + ? '' + : ` ContextualTuples: []ClientContextualTupleKey{ -${!contextualTuples - ? '' - : contextualTuples - .map( - (tuple) => - ` { +${ + !contextualTuples + ? '' + : contextualTuples + .map( + (tuple) => + ` { User: "${tuple.user}", Relation: "${tuple.relation}", Object: "${tuple.object}", },`, - ) - .join('\n') - } + ) + .join('\n') +} },` - }${context - ? ` + }${ + context + ? ` Context: &map[string]interface{}${JSON.stringify(context)},` - : '' - } + : '' + } } data, err := fgaClient.ListUsers(context.Background()). @@ -185,28 +195,31 @@ var body = new ClientListUsersRequest { Relation = "${relation}", UserFilters = new List { new() { - Type = "${userFilterType}"${userFilterRelation - ? ` + Type = "${userFilterType}"${ + userFilterRelation + ? ` Relation = "${userFilterRelation}" ` - : '' + : '' } } - }${contextualTuples - ? `, + }${ + contextualTuples + ? `, ,ContextualTuples = new List({ ${contextualTuples - .map((tuple) => `new(user: "${tuple.user}", relation: "${tuple.relation}", _object: "${tuple.object}")`) - .join(',\n ')} + .map((tuple) => `new(user: "${tuple.user}", relation: "${tuple.relation}", _object: "${tuple.object}")`) + .join(',\n ')} })` - : '' - } - ${context - ? `Context = new { ${Object.entries(context) + : '' + } + ${ + context + ? `Context = new { ${Object.entries(context) .map(([k, v]) => `${k}="${v}"`) .join(',')} }` - : '' - } + : '' + } }; var response = await fgaClient.ListUsers(body, options); @@ -259,15 +272,16 @@ var response = await fgaClient.ListUsers(body, options); "${objectId}", // list the objects that the user \`${objectId}\` "${relation}", // has an \`${relation}\` relation "${objectType}", // and that are of type \`${objectType}\` - authorization_model_id = "${modelId}", // for this particular authorization model id ${contextualTuples - ? ` + authorization_model_id = "${modelId}", // for this particular authorization model id ${ + contextualTuples + ? ` contextual_tuples = [ // Assuming the following is true ${contextualTuples - .map((tuple) => `{user = "${tuple.user}", relation = "${tuple.relation}", object = "${tuple.object}"}`) - .join(',\n ')} + .map((tuple) => `{user = "${tuple.user}", relation = "${tuple.relation}", object = "${tuple.object}"}`) + .join(',\n ')} ]` - : '' - } + : '' + } ); Reply: ${response}`; @@ -277,13 +291,13 @@ Reply: ${response}`; ? ` .contextualTupleKeys( List.of(${contextualTuples.map( - (tuple) => ` + (tuple) => ` new ClientTupleKey() .user("${tuple.user}") .relation("${tuple.relation}") ._object("${tuple.object}") ))`, - )}` + )}` : ''; const contextCall = context ? ` From 95c2389f812e119fb30870590a878739601de368 Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Fri, 10 May 2024 13:06:32 -0400 Subject: [PATCH 4/5] Fixing link --- docs/content/getting-started/perform-list-users.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/getting-started/perform-list-users.mdx b/docs/content/getting-started/perform-list-users.mdx index aabcf7d60..ea3b25f27 100644 --- a/docs/content/getting-started/perform-list-users.mdx +++ b/docs/content/getting-started/perform-list-users.mdx @@ -177,7 +177,7 @@ The performance characteristics of the ListUsers endpoint vary drastically depen ## Usersets -In the above example, only specific subjects of the `user` type were returned. However, groups of users, known as [usersets](./modeling/building-blocks/usersets.mdx), can also be returned from the List Users API. This is done by specifying a `relation` field in the `user_filters` request object. Usersets will only expand to the underlying subjects if that `type` is specified as the user filter object. +In the above example, only specific subjects of the `user` type were returned. However, groups of users, known as [usersets](../modeling/usersets.mdx), can also be returned from the List Users API. This is done by specifying a `relation` field in the `user_filters` request object. Usersets will only expand to the underlying subjects if that `type` is specified as the user filter object. Below is an example where usersets can be returned: From 4de69fb920f0d2b30165f48e66b2541aedd04437 Mon Sep 17 00:00:00 2001 From: Will Vedder Date: Fri, 10 May 2024 13:16:54 -0400 Subject: [PATCH 5/5] Actually fixing link --- docs/content/getting-started/perform-list-users.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/getting-started/perform-list-users.mdx b/docs/content/getting-started/perform-list-users.mdx index ea3b25f27..cf22ee172 100644 --- a/docs/content/getting-started/perform-list-users.mdx +++ b/docs/content/getting-started/perform-list-users.mdx @@ -177,7 +177,7 @@ The performance characteristics of the ListUsers endpoint vary drastically depen ## Usersets -In the above example, only specific subjects of the `user` type were returned. However, groups of users, known as [usersets](../modeling/usersets.mdx), can also be returned from the List Users API. This is done by specifying a `relation` field in the `user_filters` request object. Usersets will only expand to the underlying subjects if that `type` is specified as the user filter object. +In the above example, only specific subjects of the `user` type were returned. However, groups of users, known as [usersets](../modeling/building-blocks/usersets.mdx), can also be returned from the List Users API. This is done by specifying a `relation` field in the `user_filters` request object. Usersets will only expand to the underlying subjects if that `type` is specified as the user filter object. Below is an example where usersets can be returned: