Skip to content

Commit

Permalink
release: v0.3.4 (#72)
Browse files Browse the repository at this point in the history
* chore(deps): update dependencies

* fix: WriteAuthorizationModel was not passing conditions to API

* feat: configurable client credentials token url

* release: v0.3.4

---------

Co-authored-by: Yann D'Isanto <ydisanto@gmail.com>
  • Loading branch information
rhamzeh and le-yams authored Jan 22, 2024
1 parent 098688e commit f50d59c
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 60 deletions.
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
# Changelog

## v0.3.4

### [0.3.4](https://github.com/openfga/go-sdk/compare/v0.3.3...v0.3.4) (2024-01-22)

- feat: configurable client credentials token url - thanks @le-yams
- fix: WriteAuthorizationModel was not passing conditions to API

## v0.3.3

### [0.3.3](https://github.com/openfga/go-sdk/compare/v0.3.2...v0.3.3) (2023-12-21)

- fix: WriteAuthorizationModel was not passing conditions to API
- chore: add example project
- chore: add [example project](./example)

## v0.3.2

Expand All @@ -25,7 +32,7 @@

### [0.3.0](https://github.com/openfga/go-sdk/compare/v0.2.3...v0.3.0) (2023-12-11)

- feat!: initial support for conditions
- feat!: initial support for [conditions](https://openfga.dev/blog/conditional-tuples-announcement)
- 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`)
- fix: resolve a bug in `NewCredentials` (#60) - thanks @harper
- chore!: use latest API interfaces
Expand Down
74 changes: 43 additions & 31 deletions api_open_fga_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ func TestOpenFgaApiConfiguration(t *testing.T) {
}
})

clientCredentialsFirstRequestTest := func(t *testing.T, config Configuration) {
clientCredentialsFirstRequestTest := func(t *testing.T, config Configuration, expectedTokenEndpoint string) {
configuration, err := NewConfiguration(config)
if err != nil {
t.Fatalf("%v", err)
Expand Down Expand Up @@ -275,7 +275,7 @@ func TestOpenFgaApiConfiguration(t *testing.T) {
},
)

httpmock.RegisterResponder("POST", fmt.Sprintf("https://%s/oauth/token", configuration.Credentials.Config.ClientCredentialsApiTokenIssuer),
httpmock.RegisterResponder("POST", expectedTokenEndpoint,
func(req *http.Request) (*http.Response, error) {
resp, err := httpmock.NewJsonResponse(200, struct {
AccessToken string `json:"access_token"`
Expand All @@ -292,7 +292,7 @@ func TestOpenFgaApiConfiguration(t *testing.T) {
}

info := httpmock.GetCallCountInfo()
numCalls := info[fmt.Sprintf("POST https://%s/oauth/token", configuration.Credentials.Config.ClientCredentialsApiTokenIssuer)]
numCalls := info[fmt.Sprintf("POST %s", expectedTokenEndpoint)]
if numCalls != 1 {
t.Fatalf("Expected call to get access token to be made exactly once, saw: %d", numCalls)
}
Expand All @@ -302,39 +302,51 @@ func TestOpenFgaApiConfiguration(t *testing.T) {
}
}

t.Run("should issue a network call to get the token at the first request if client id is provided", func(t *testing.T) {
t.Run("with Auth0 configuration", func(t *testing.T) {
clientCredentialsFirstRequestTest(t, Configuration{
ApiHost: "api.fga.example",
StoreId: "01GXSB9YR785C4FYS3C0RTG7B2",
Credentials: &credentials.Credentials{
Method: credentials.CredentialsMethodClientCredentials,
Config: &credentials.Config{
ClientCredentialsClientId: "some-id",
ClientCredentialsClientSecret: "some-secret",
ClientCredentialsApiAudience: "some-audience",
ClientCredentialsApiTokenIssuer: "tokenissuer.fga.example",
tokenIssuers := map[string]string{
"issuer.fga.example": "https://issuer.fga.example/oauth/token",
"https://issuer.fga.example": "https://issuer.fga.example/oauth/token",
"https://issuer.fga.example/": "https://issuer.fga.example/oauth/token",
"https://issuer.fga.example:8080": "https://issuer.fga.example:8080/oauth/token",
"https://issuer.fga.example:8080/": "https://issuer.fga.example:8080/oauth/token",
"issuer.fga.example/some_endpoint": "https://issuer.fga.example/some_endpoint",
"https://issuer.fga.example/some_endpoint": "https://issuer.fga.example/some_endpoint",
"https://issuer.fga.example:8080/some_endpoint": "https://issuer.fga.example:8080/some_endpoint",
}

for tokenIssuer, expectedTokenURL := range tokenIssuers {
t.Run("should issue a network call to get the token at the first request if client id is provided", func(t *testing.T) {
t.Run("with Auth0 configuration", func(t *testing.T) {
clientCredentialsFirstRequestTest(t, Configuration{
ApiUrl: "http://api.fga.example",
StoreId: "01GXSB9YR785C4FYS3C0RTG7B2",
Credentials: &credentials.Credentials{
Method: credentials.CredentialsMethodClientCredentials,
Config: &credentials.Config{
ClientCredentialsClientId: "some-id",
ClientCredentialsClientSecret: "some-secret",
ClientCredentialsApiAudience: "some-audience",
ClientCredentialsApiTokenIssuer: tokenIssuer,
},
},
},
}, expectedTokenURL)
})
})
t.Run("with OAuth2 configuration", func(t *testing.T) {
clientCredentialsFirstRequestTest(t, Configuration{
ApiHost: "api.fga.example",
StoreId: "01GXSB9YR785C4FYS3C0RTG7B2",
Credentials: &credentials.Credentials{
Method: credentials.CredentialsMethodClientCredentials,
Config: &credentials.Config{
ClientCredentialsClientId: "some-id",
ClientCredentialsClientSecret: "some-secret",
ClientCredentialsScopes: "scope1 scope2",
ClientCredentialsApiTokenIssuer: "tokenissuer.fga.example",
t.Run("with OAuth2 configuration", func(t *testing.T) {
clientCredentialsFirstRequestTest(t, Configuration{
ApiUrl: "http://api.fga.example",
StoreId: "01GXSB9YR785C4FYS3C0RTG7B2",
Credentials: &credentials.Credentials{
Method: credentials.CredentialsMethodClientCredentials,
Config: &credentials.Config{
ClientCredentialsClientId: "some-id",
ClientCredentialsClientSecret: "some-secret",
ClientCredentialsScopes: "scope1 scope2",
ClientCredentialsApiTokenIssuer: tokenIssuer,
},
},
},
}, expectedTokenURL)
})
})
})

}
t.Run("should not issue a network call to get the token at the first request if the clientId is not provided", func(t *testing.T) {
configuration, err := NewConfiguration(Configuration{
ApiHost: "api.fga.example",
Expand Down
5 changes: 1 addition & 4 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -822,10 +822,7 @@ func (request *SdkClientWriteAuthorizationModelRequest) GetContext() _context.Co
}

func (client *OpenFgaClient) WriteAuthorizationModelExecute(request SdkClientWriteAuthorizationModelRequestInterface) (*ClientWriteAuthorizationModelResponse, error) {
data, _, err := client.OpenFgaApi.WriteAuthorizationModel(request.GetContext()).Body(fgaSdk.WriteAuthorizationModelRequest{
TypeDefinitions: request.GetBody().TypeDefinitions,
SchemaVersion: request.GetBody().SchemaVersion,
}).Execute()
data, _, err := client.OpenFgaApi.WriteAuthorizationModel(request.GetContext()).Body(*request.GetBody()).Execute()
if err != nil {
return nil, err
}
Expand Down
216 changes: 216 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,222 @@ func TestOpenFgaClient(t *testing.T) {
}
})

t.Run("WriteAuthorizationModelWithCondition", func(t *testing.T) {
test := TestDefinition{
Name: "WriteAuthorizationModelWithCondition",
JsonResponse: `{"authorization_model_id":"01GXSA8YR785C4FYS3C0RTG7B1"}`,
ResponseStatus: http.StatusOK,
Method: http.MethodPost,
RequestPath: "authorization-models",
}
requestBody := ClientWriteAuthorizationModelRequest{
SchemaVersion: "1.1",
TypeDefinitions: []openfga.TypeDefinition{
{
Type: "user",
Relations: &map[string]openfga.Userset{},
},
{
Type: "document",
Relations: &map[string]openfga.Userset{
"writer": {This: &map[string]interface{}{}},
"viewer": {Union: &openfga.Usersets{
Child: []openfga.Userset{
{This: &map[string]interface{}{}},
{ComputedUserset: &openfga.ObjectRelation{
Object: openfga.PtrString(""),
Relation: openfga.PtrString("writer"),
}},
},
}},
},
Metadata: &openfga.Metadata{
Relations: &map[string]openfga.RelationMetadata{
"writer": {
DirectlyRelatedUserTypes: &[]openfga.RelationReference{
{Type: "user"},
{Type: "user", Condition: openfga.PtrString("ViewCountLessThan200")},
},
},
"viewer": {
DirectlyRelatedUserTypes: &[]openfga.RelationReference{
{Type: "user"},
},
},
},
},
},
},
Conditions: &map[string]openfga.Condition{
"ViewCountLessThan200": {
Name: "ViewCountLessThan200",
Expression: "ViewCount < 200",
Parameters: &map[string]openfga.ConditionParamTypeRef{
"ViewCount": {
TypeName: openfga.INT,
},
"Type": {
TypeName: openfga.STRING,
},
"Name": {
TypeName: openfga.STRING,
},
},
},
},
}

var expectedResponse openfga.WriteAuthorizationModelResponse
if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil {
t.Fatalf("%v", err)
}

httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterMatcherResponder(
test.Method,
fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath),
httpmock.BodyContainsString(`"ViewCountLessThan200"`),
func(req *http.Request) (*http.Response, error) {
resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse)
if err != nil {
return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil
}
return resp, nil
},
)
options := ClientWriteAuthorizationModelOptions{}
got, err := fgaClient.WriteAuthorizationModel(context.Background()).Body(requestBody).Options(options).Execute()
if err != nil {
t.Fatalf("%v", err)
}

_, err = got.MarshalJSON()
if err != nil {
t.Fatalf("%v", err)
}

if got.GetAuthorizationModelId() != expectedResponse.GetAuthorizationModelId() {
t.Fatalf("OpenFgaClient.%v() / AuthorizationModelId = %v, want %v", test.Name, got.GetAuthorizationModelId(), expectedResponse.GetAuthorizationModelId())
}

// WriteAuthorizationModel without options should work
_, err = fgaClient.WriteAuthorizationModel(context.Background()).Body(requestBody).Execute()
if err != nil {
t.Fatalf("%v", err)
}
})

t.Run("WriteAuthorizationModelWithCondition2", func(t *testing.T) {
test := TestDefinition{
Name: "WriteAuthorizationModelWithCondition2",
JsonResponse: `{"authorization_model_id":"01GXSA8YR785C4FYS3C0RTG7B1"}`,
ResponseStatus: http.StatusOK,
Method: http.MethodPost,
RequestPath: "authorization-models",
}

schemaVersion := "1.1"
typeDefs := []openfga.TypeDefinition{
{
Type: "user",
Relations: &map[string]openfga.Userset{},
},
{
Type: "document",
Relations: &map[string]openfga.Userset{
"writer": {This: &map[string]interface{}{}},
"viewer": {Union: &openfga.Usersets{
Child: []openfga.Userset{
{This: &map[string]interface{}{}},
{ComputedUserset: &openfga.ObjectRelation{
Object: openfga.PtrString(""),
Relation: openfga.PtrString("writer"),
}},
},
}},
},
Metadata: &openfga.Metadata{
Relations: &map[string]openfga.RelationMetadata{
"writer": {
DirectlyRelatedUserTypes: &[]openfga.RelationReference{
{Type: "user"},
{Type: "user", Condition: openfga.PtrString("ViewCountLessThan200")},
},
},
"viewer": {
DirectlyRelatedUserTypes: &[]openfga.RelationReference{
{Type: "user"},
},
},
},
},
},
}
conditions := map[string]openfga.Condition{
"ViewCountLessThan200": {
Name: "ViewCountLessThan200",
Expression: "ViewCount < 200",
Parameters: &map[string]openfga.ConditionParamTypeRef{
"ViewCount": {
TypeName: openfga.INT,
},
"Type": {
TypeName: openfga.STRING,
},
"Name": {
TypeName: openfga.STRING,
},
},
},
}
requestBody := ClientWriteAuthorizationModelRequest{
SchemaVersion: schemaVersion,
TypeDefinitions: typeDefs,
Conditions: &conditions,
}

var expectedResponse openfga.WriteAuthorizationModelResponse
if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil {
t.Fatalf("%v", err)
}

httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterMatcherResponder(
test.Method,
fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath),
httpmock.BodyContainsString(`"ViewCountLessThan200"`),
func(req *http.Request) (*http.Response, error) {
resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse)
if err != nil {
return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil
}
return resp, nil
},
)
options := ClientWriteAuthorizationModelOptions{}
got, err := fgaClient.WriteAuthorizationModel(context.Background()).Body(requestBody).Options(options).Execute()
if err != nil {
t.Fatalf("%v", err)
}

_, err = got.MarshalJSON()
if err != nil {
t.Fatalf("%v", err)
}

if got.GetAuthorizationModelId() != expectedResponse.GetAuthorizationModelId() {
t.Fatalf("OpenFgaClient.%v() / AuthorizationModelId = %v, want %v", test.Name, got.GetAuthorizationModelId(), expectedResponse.GetAuthorizationModelId())
}

// WriteAuthorizationModel without options should work
_, err = fgaClient.WriteAuthorizationModel(context.Background()).Body(requestBody).Execute()
if err != nil {
t.Fatalf("%v", err)
}
})

t.Run("ReadAuthorizationModel", func(t *testing.T) {
test := TestDefinition{
Name: "ReadAuthorizationModel",
Expand Down
4 changes: 2 additions & 2 deletions configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import (
)

const (
SdkVersion = "0.3.3"
SdkVersion = "0.3.4"

defaultUserAgent = "openfga-sdk go/0.3.3"
defaultUserAgent = "openfga-sdk go/0.3.4"
)

// RetryParams configures configuration for retry in case of HTTP too many request
Expand Down
Loading

0 comments on commit f50d59c

Please sign in to comment.