Skip to content

Commit

Permalink
fix: use correct content type for token request (#43)
Browse files Browse the repository at this point in the history
## Description

Moves the client to use `application/x-www-form-urlencoded` for the
content type when making token requests per the spec.

In order to do this we needed to move from using `StringContent` to
`FormUrlEncodedContent` and then the runtime will handle setting the
content type header for us.

Also syncs some changes from the sdk-generator repo.

## References

Part of openfga/sdk-generator#284
Generated from openfga/sdk-generator#308

## Review Checklist
- [x] I have clicked on ["allow edits by
maintainers"](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork).
- [ ] I have added documentation for new/changed functionality in this
PR or in a PR to [openfga.dev](https://github.com/openfga/openfga.dev)
[Provide a link to any relevant PRs in the references section above]
- [x] The correct base branch is being used, if not `main`
- [ ] I have added tests to validate that the change in functionality is
working as expected
  • Loading branch information
rhamzeh authored Feb 12, 2024
2 parents 5d02e94 + 003d525 commit cf1bdc6
Show file tree
Hide file tree
Showing 7 changed files with 25 additions and 22 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## v0.3.0

### [0.3.0](https://github.com/openfga/dotnet-sdk/compare/v0.2.5...v0.3.0) (2023-12-20)
- feat!: initial support for conditions
- feat!: initial support for [conditions](https://openfga.dev/blog/conditional-tuples-announcement)
- feat!: allow overriding storeId per request (#33)
- feat: support specifying a port and path for the API (You can now set the `ApiUrl` to something like: `https://api.fga.exampleL8080/some_path`)
- feat: validate that store id and auth model id in ulid format (#23)
Expand All @@ -12,6 +12,7 @@
- fix: `OpenFgaClient.Read` and `OpenFgaClient.ReadChanges` now allow a null body
- chore!: use latest API interfaces
- chore: dependency updates
- chore: add [example project](./example)

BREAKING CHANGES:
Note: This release comes with substantial breaking changes, especially to the interfaces due to the protobuf changes in the last release.
Expand Down
2 changes: 1 addition & 1 deletion docs/OpenFgaApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ Name | Type | Description | Notes
Get tuples from the store that matches a query, without following userset rewrite rules

The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. It is different from the `/stores/{store_id}/expand` API in that it only returns relationship tuples that are stored in the system and satisfy the query. In the body: 1. `tuple_key` is optional. If not specified, it will return all tuples in the store. 2. `tuple_key.object` is mandatory if `tuple_key` is specified. It can be a full object (e.g., `type:object_id`) or type only (e.g., `type:`). 3. `tuple_key.user` is mandatory if tuple_key is specified in the case the `tuple_key.object` is a type only. ## Examples ### Query for all objects in a type definition To query for all objects that `user:bob` has `reader` relationship in the `document` type definition, call read API with body of ```json { \"tuple_key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:\" } } ``` The API will return tuples and a continuation token, something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `user:bob` has a `reader` relationship with 1 document `document:2021-budget`. Note that this API, unlike the List Objects API, does not evaluate the tuples in the store. The continuation token will be empty if there are no more tuples to query. ### Query for all stored relationship tuples that have a particular relation and object To query for all users that have `reader` relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`). Note that, even if the model said that all `writers` are also `readers`, the API will not return writers such as `user:anne` because it only returns tuples and does not evaluate them. ### Query for all users with all relationships for a particular document To query for all users that have any relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-05T13:42:12.356Z\" }, { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`) and 1 `writer` (`user:anne`).
The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. The API doesn't guarantee order by any field. It is different from the `/stores/{store_id}/expand` API in that it only returns relationship tuples that are stored in the system and satisfy the query. In the body: 1. `tuple_key` is optional. If not specified, it will return all tuples in the store. 2. `tuple_key.object` is mandatory if `tuple_key` is specified. It can be a full object (e.g., `type:object_id`) or type only (e.g., `type:`). 3. `tuple_key.user` is mandatory if tuple_key is specified in the case the `tuple_key.object` is a type only. ## Examples ### Query for all objects in a type definition To query for all objects that `user:bob` has `reader` relationship in the `document` type definition, call read API with body of ```json { \"tuple_key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:\" } } ``` The API will return tuples and a continuation token, something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `user:bob` has a `reader` relationship with 1 document `document:2021-budget`. Note that this API, unlike the List Objects API, does not evaluate the tuples in the store. The continuation token will be empty if there are no more tuples to query. ### Query for all stored relationship tuples that have a particular relation and object To query for all users that have `reader` relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`). Note that, even if the model said that all `writers` are also `readers`, the API will not return writers such as `user:anne` because it only returns tuples and does not evaluate them. ### Query for all users with all relationships for a particular document To query for all users that have any relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-05T13:42:12.356Z\" }, { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`) and 1 `writer` (`user:anne`).

### Example
```csharp
Expand Down
2 changes: 1 addition & 1 deletion example/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ run:

run-openfga:
docker pull docker.io/openfga/openfga:${openfga_version} && \
docker run -p 8080:8080 docker.io/openfga/openfga:${openfga_version}
docker run -p 8080:8080 docker.io/openfga/openfga:${openfga_version} run
12 changes: 8 additions & 4 deletions src/OpenFga.Sdk.Test/Api/OpenFgaApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ public async Task ExchangeCredentialsTest() {
"SendAsync",
ItExpr.Is<HttpRequestMessage>(req =>
req.RequestUri == new Uri($"https://{config.Credentials.Config.ApiTokenIssuer}/oauth/token") &&
req.Method == HttpMethod.Post),
req.Method == HttpMethod.Post &&
req.Content.Headers.ContentType.ToString().Equals("application/x-www-form-urlencoded")),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage() {
Expand Down Expand Up @@ -358,7 +359,8 @@ public async Task ExchangeCredentialsTest() {
Times.Exactly(1),
ItExpr.Is<HttpRequestMessage>(req =>
req.RequestUri == new Uri($"https://{config.Credentials.Config.ApiTokenIssuer}/oauth/token") &&
req.Method == HttpMethod.Post),
req.Method == HttpMethod.Post &&
req.Content.Headers.ContentType.ToString().Equals("application/x-www-form-urlencoded")),
ItExpr.IsAny<CancellationToken>()
);
mockHandler.Protected().Verify(
Expand Down Expand Up @@ -401,7 +403,8 @@ public async Task ExchangeCredentialsAfterExpiryTest() {
"SendAsync",
ItExpr.Is<HttpRequestMessage>(req =>
req.RequestUri == new Uri($"https://{config.Credentials.Config.ApiTokenIssuer}/oauth/token") &&
req.Method == HttpMethod.Post),
req.Method == HttpMethod.Post &&
req.Content.Headers.ContentType.ToString().Equals("application/x-www-form-urlencoded")),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage() {
Expand Down Expand Up @@ -455,7 +458,8 @@ public async Task ExchangeCredentialsAfterExpiryTest() {
Times.Exactly(2),
ItExpr.Is<HttpRequestMessage>(req =>
req.RequestUri == new Uri($"https://{config.Credentials.Config.ApiTokenIssuer}/oauth/token") &&
req.Method == HttpMethod.Post),
req.Method == HttpMethod.Post &&
req.Content.Headers.ContentType.ToString().Equals("application/x-www-form-urlencoded")),
ItExpr.IsAny<CancellationToken>()
);
mockHandler.Protected().Verify(
Expand Down
Loading

0 comments on commit cf1bdc6

Please sign in to comment.