From 69d8eba0124fd821ea1867fa4609e7fedf52763d Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Wed, 3 Jul 2024 17:03:38 -0700 Subject: [PATCH 01/17] Fix typo for IdempotencyKey json encoding --- pkg/organizations/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/organizations/client.go b/pkg/organizations/client.go index f68e9d27..c4a5fbf8 100644 --- a/pkg/organizations/client.go +++ b/pkg/organizations/client.go @@ -163,7 +163,7 @@ type CreateOrganizationOpts struct { DomainData []OrganizationDomainData `json:"domain_data"` // Optional unique identifier to ensure idempotency - IdempotencyKey string `json:"idempotency_iey,omitempty"` + IdempotencyKey string `json:"idempotency_key,omitempty"` } // UpdateOrganizationOpts contains the options to update an Organization. From 01506ca3a7f4de0890658199b47640421f8b410c Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Wed, 3 Jul 2024 17:23:17 -0700 Subject: [PATCH 02/17] Add FGA client and methods --- pkg/fga/client.go | 822 ++++++++++++++ pkg/fga/client_live_example.go | 1860 ++++++++++++++++++++++++++++++++ pkg/fga/client_test.go | 1073 ++++++++++++++++++ pkg/fga/fga.go | 111 ++ pkg/fga/fga_test.go | 348 ++++++ 5 files changed, 4214 insertions(+) create mode 100644 pkg/fga/client.go create mode 100644 pkg/fga/client_live_example.go create mode 100644 pkg/fga/client_test.go create mode 100644 pkg/fga/fga.go create mode 100644 pkg/fga/fga_test.go diff --git a/pkg/fga/client.go b/pkg/fga/client.go new file mode 100644 index 00000000..0a9e0272 --- /dev/null +++ b/pkg/fga/client.go @@ -0,0 +1,822 @@ +package fga + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "sync" + "time" + + "github.com/google/go-querystring/query" + "github.com/workos/workos-go/v4/internal/workos" + "github.com/workos/workos-go/v4/pkg/common" + "github.com/workos/workos-go/v4/pkg/workos_errors" +) + +// ResponseLimit is the default number of records to limit a response to. +const ResponseLimit = 10 + +// Order represents the order of records. +type Order string + +// Constants that enumerate the available orders. +const ( + Asc Order = "asc" + Desc Order = "desc" +) + +// Client represents a client that performs FGA requests to the WorkOS API. +type Client struct { + // The WorkOS API Key. It can be found in https://dashboard.workos.com/api-keys. + APIKey string + + // The http.Client that is used to get Directory Sync records from WorkOS. + // Defaults to http.Client. + HTTPClient *http.Client + + // The endpoint to WorkOS API. Defaults to https://api.workos.com. + Endpoint string + + // The function used to encode in JSON. Defaults to json.Marshal. + JSONEncode func(v interface{}) ([]byte, error) + + once sync.Once +} + +func (c *Client) init() { + if c.HTTPClient == nil { + c.HTTPClient = &http.Client{Timeout: 10 * time.Second} + } + + if c.Endpoint == "" { + c.Endpoint = "https://api.workos.com" + } + + if c.JSONEncode == nil { + c.JSONEncode = json.Marshal + } +} + +// Objects +type Object struct { + // The type of the object. + ObjectType string `json:"object_type"` + + // The customer defined string identifier for this object. + ObjectId string `json:"object_id"` + + // Map containing additional information about this object. + Meta map[string]interface{} `json:"meta"` +} + +type GetObjectOpts struct { + // The type of the object. + ObjectType string + + // The customer defined string identifier for this object. + ObjectId string +} + +type ListObjectsOpts struct { + // The type of the object. + ObjectType string `url:"object_type,omitempty"` + + // Searchable text for an Object. Can be empty. + Search string `url:"search,omitempty"` + + // Maximum number of records to return. + Limit int `url:"limit,omitempty"` + + // The order in which to paginate records. + Order Order `url:"order,omitempty"` + + // Pagination cursor to receive records before a provided Object ID. + Before string `url:"before,omitempty"` + + // Pagination cursor to receive records after a provided Object ID. + After string `url:"after,omitempty"` +} + +// ListObjectsResponse describes the response structure when requesting Objects +type ListObjectsResponse struct { + // List of provisioned Objects. + Data []Object `json:"data"` + + // Cursor pagination options. + ListMetadata common.ListMetadata `json:"list_metadata"` +} + +type CreateObjectOpts struct { + // The type of the object. + ObjectType string `json:"object_type"` + + // The customer defined string identifier for this object. + ObjectId string `json:"object_id,omitempty"` + + // Map containing additional information about this object. + Meta map[string]interface{} `json:"meta,omitempty"` +} + +type UpdateObjectOpts struct { + // The type of the object. + ObjectType string `json:"object_type"` + + // The customer defined string identifier for this object. + ObjectId string `json:"object_id,omitempty"` + + // Map containing additional information about this object. + Meta map[string]interface{} `json:"meta,omitempty"` +} + +// DeleteObjectOpts contains the options to delete an object. +type DeleteObjectOpts struct { + // The type of the object. + ObjectType string + + // The customer defined string identifier for this object. + ObjectId string +} + +// Warrants +type Subject struct { + // The type of the subject. + ObjectType string `json:"object_type"` + + // The customer defined string identifier for this subject. + ObjectId string `json:"object_id"` + + // The relation of the subject. + Relation string `json:"relation,omitempty"` +} + +type Warrant struct { + // Type of object to assign a relation to. Must be an existing type. + ObjectType string `json:"object_type"` + + // Id of the object to assign a relation to. + ObjectId string `json:"object_id"` + + // Relation to assign to the object. + Relation string `json:"relation"` + + // Subject of the warrant + Subject Subject `json:"subject"` + + // Policy that must evaluate to true for warrant to be valid + Policy string `json:"policy,omitempty"` +} + +type ListWarrantsOpts struct { + // Only return warrants whose objectType matches this value. + ObjectType string `url:"object_type,omitempty"` + + // Only return warrants whose objectId matches this value. + ObjectId string `url:"object_id,omitempty"` + + // Only return warrants whose relation matches this value. + Relation string `url:"relation,omitempty"` + + // Only return warrants with a subject whose objectType matches this value. + SubjectType string `url:"subject_type,omitempty"` + + // Only return warrants with a subject whose objectId matches this value. + SubjectId string `url:"subject_id,omitempty"` + + // Only return warrants with a subject whose relation matches this value. + SubjectRelation string `url:"subject_relation,omitempty"` + + // Maximum number of records to return. + Limit int `url:"limit,omitempty"` + + // Pagination cursor to receive records after a provided Warrant ID. + After string `url:"after,omitempty"` + + // Optional token to specify desired read consistency + WarrantToken string `url:"-"` +} + +// ListWarrantsResponse describes the response structure when requesting Warrants +type ListWarrantsResponse struct { + // List of provisioned Warrants. + Data []Warrant `json:"data"` + + // Cursor pagination options. + ListMetadata common.ListMetadata `json:"list_metadata"` +} + +type WriteWarrantOpts struct { + // Operation to perform for the given warrant + Op string `json:"op,omitempty"` + + // Type of object to assign a relation to. Must be an existing type. + ObjectType string `json:"object_type"` + + // Id of the object to assign a relation to. + ObjectId string `json:"object_id"` + + // Relation to assign to the object. + Relation string `json:"relation"` + + // Subject of the warrant + Subject Subject `json:"subject"` + + // Policy that must evaluate to true for warrant to be valid + Policy string `json:"policy,omitempty"` +} + +type WriteWarrantResponse struct { + WarrantToken string `json:"warrant_token"` +} + +// Check +type Context map[string]interface{} + +type WarrantCheck struct { + // The type of the object. + ObjectType string `json:"object_type"` + + // Id of the specific object. + ObjectId string `json:"object_id"` + + // Relation to check between the object and subject. + Relation string `json:"relation"` + + // The subject that must have the specified relation. + Subject Subject `json:"subject"` + + // Contextual data to use for the access check. + Context Context `json:"context,omitempty"` +} + +type CheckOpts struct { + // Warrant to check + Warrant WarrantCheck `json:"warrant_check"` + + // Flag to include debug information in the response. + Debug bool `json:"debug,omitempty"` + + // Optional token to specify desired read consistency + WarrantToken string `json:"-"` +} + +type CheckManyOpts struct { + // The operator to use for the given warrants. + Op string `json:"op,omitempty"` + + // List of warrants to check. + Warrants []WarrantCheck `json:"warrants"` + + // Flag to include debug information in the response. + Debug bool `json:"debug,omitempty"` + + // Optional token to specify desired read consistency + WarrantToken string `json:"-"` +} + +type BatchCheckOpts struct { + // List of warrants to check. + Warrants []WarrantCheck `json:"warrants"` + + // Flag to include debug information in the response. + Debug bool `json:"debug,omitempty"` + + // Optional token to specify desired read consistency + WarrantToken string `json:"-"` +} + +type CheckResponse struct { + Code int64 `json:"code"` + Result string `json:"result"` + IsImplicit bool `json:"is_implicit"` + ProcessingTime int64 `json:"processing_time,omitempty"` + DecisionPath map[string][]Warrant `json:"decision_path,omitempty"` +} + +// Query +type QueryOpts struct { + // Query to be executed. + Query string `url:"q"` + + // Contextual data to use for the query. + Context Context `url:"context,omitempty"` + + // Maximum number of records to return. + Limit int `url:"limit,omitempty"` + + // The order in which to paginate records. + Order Order `url:"order,omitempty"` + + // Pagination cursor to receive records before a provided Warrant ID. + Before string `url:"before,omitempty"` + + // Pagination cursor to receive records after a provided Warrant ID. + After string `url:"after,omitempty"` + + // Optional token to specify desired read consistency + WarrantToken string `url:"-"` +} + +type QueryResult struct { + // The type of the object. + ObjectType string `json:"object_type"` + + // Id of the specific object. + ObjectId string `json:"object_id"` + + // Relation between the object and subject. + Relation string `json:"relation"` + + // Warrant matching the provided query + Warrant Warrant `json:"warrant"` + + // Specifies whether the warrant is implicitly defined. + IsImplicit bool `json:"is_implicit"` + + // Metadata of the object. + Meta map[string]interface{} `json:"meta,omitempty"` +} + +type QueryResponse struct { + // List of query results. + Data []QueryResult `json:"data"` + + // Cursor pagination options. + ListMetadata common.ListMetadata `json:"list_metadata"` +} + +// GetObject gets an Object. +func (c *Client) GetObject(ctx context.Context, opts GetObjectOpts) (Object, error) { + c.once.Do(c.init) + + endpoint := fmt.Sprintf("%s/fga/v1/objects/%s/%s", c.Endpoint, opts.ObjectType, opts.ObjectId) + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return Object{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + + res, err := c.HTTPClient.Do(req) + if err != nil { + return Object{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return Object{}, err + } + + var body Object + dec := json.NewDecoder(res.Body) + err = dec.Decode(&body) + return body, err +} + +// ListObjects gets a list of FGA objects. +func (c *Client) ListObjects(ctx context.Context, opts ListObjectsOpts) (ListObjectsResponse, error) { + c.once.Do(c.init) + + endpoint := fmt.Sprintf("%s/fga/v1/objects", c.Endpoint) + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return ListObjectsResponse{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + + if opts.Limit == 0 { + opts.Limit = ResponseLimit + } + + if opts.Order == "" { + opts.Order = Desc + } + + q, err := query.Values(opts) + if err != nil { + return ListObjectsResponse{}, err + } + + req.URL.RawQuery = q.Encode() + + res, err := c.HTTPClient.Do(req) + if err != nil { + return ListObjectsResponse{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return ListObjectsResponse{}, err + } + + var body ListObjectsResponse + dec := json.NewDecoder(res.Body) + err = dec.Decode(&body) + return body, err +} + +// CreateObject creates a new object +func (c *Client) CreateObject(ctx context.Context, opts CreateObjectOpts) (Object, error) { + c.once.Do(c.init) + + data, err := c.JSONEncode(opts) + if err != nil { + return Object{}, err + } + + endpoint := fmt.Sprintf("%s/fga/v1/objects", c.Endpoint) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(data)) + if err != nil { + return Object{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + + res, err := c.HTTPClient.Do(req) + if err != nil { + return Object{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return Object{}, err + } + + var body Object + dec := json.NewDecoder(res.Body) + err = dec.Decode(&body) + return body, err +} + +// UpdateObject updates an existing Object +func (c *Client) UpdateObject(ctx context.Context, opts UpdateObjectOpts) (Object, error) { + c.once.Do(c.init) + + // UpdateObjectChangeOpts contains the options to update an Object minus the ObjectType and ObjectId + type UpdateObjectChangeOpts struct { + Meta map[string]interface{} `json:"meta"` + } + + update_opts := UpdateObjectChangeOpts{Meta: opts.Meta} + + data, err := c.JSONEncode(update_opts) + if err != nil { + return Object{}, err + } + + endpoint := fmt.Sprintf("%s/fga/v1/objects/%s/%s", c.Endpoint, opts.ObjectType, opts.ObjectId) + req, err := http.NewRequest(http.MethodPut, endpoint, bytes.NewBuffer(data)) + if err != nil { + return Object{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + + res, err := c.HTTPClient.Do(req) + if err != nil { + return Object{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return Object{}, err + } + + var body Object + dec := json.NewDecoder(res.Body) + err = dec.Decode(&body) + return body, err + +} + +// DeleteObject deletes an Object +func (c *Client) DeleteObject(ctx context.Context, opts DeleteObjectOpts) error { + c.once.Do(c.init) + + endpoint := fmt.Sprintf("%s/fga/v1/objects/%s/%s", c.Endpoint, opts.ObjectType, opts.ObjectId) + req, err := http.NewRequest(http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + + res, err := c.HTTPClient.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + return workos_errors.TryGetHTTPError(res) +} + +// ListWarrants gets a list of Warrants. +func (c *Client) ListWarrants(ctx context.Context, opts ListWarrantsOpts) (ListWarrantsResponse, error) { + c.once.Do(c.init) + + endpoint := fmt.Sprintf("%s/fga/v1/warrants", c.Endpoint) + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return ListWarrantsResponse{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + if opts.WarrantToken != "" { + req.Header.Set("Warrant-Token", opts.WarrantToken) + } + + if opts.Limit == 0 { + opts.Limit = ResponseLimit + } + + q, err := query.Values(opts) + if err != nil { + return ListWarrantsResponse{}, err + } + + req.URL.RawQuery = q.Encode() + + res, err := c.HTTPClient.Do(req) + if err != nil { + return ListWarrantsResponse{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return ListWarrantsResponse{}, err + } + + var body ListWarrantsResponse + dec := json.NewDecoder(res.Body) + err = dec.Decode(&body) + return body, err +} + +// WriteWarrant performs a write operation on a Warrant. +func (c *Client) WriteWarrant(ctx context.Context, opts WriteWarrantOpts) (WriteWarrantResponse, error) { + c.once.Do(c.init) + + data, err := c.JSONEncode(opts) + if err != nil { + return WriteWarrantResponse{}, err + } + + endpoint := fmt.Sprintf("%s/fga/v1/warrants", c.Endpoint) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(data)) + if err != nil { + return WriteWarrantResponse{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + + res, err := c.HTTPClient.Do(req) + if err != nil { + return WriteWarrantResponse{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return WriteWarrantResponse{}, err + } + + var body WriteWarrantResponse + dec := json.NewDecoder(res.Body) + err = dec.Decode(&body) + return body, err +} + +// BatchWriteWarrants performs a write operation on a Warrant. +func (c *Client) BatchWriteWarrants(ctx context.Context, opts []WriteWarrantOpts) (WriteWarrantResponse, error) { + c.once.Do(c.init) + + data, err := c.JSONEncode(opts) + if err != nil { + return WriteWarrantResponse{}, err + } + + endpoint := fmt.Sprintf("%s/fga/v1/warrants", c.Endpoint) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(data)) + if err != nil { + return WriteWarrantResponse{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + + res, err := c.HTTPClient.Do(req) + if err != nil { + return WriteWarrantResponse{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return WriteWarrantResponse{}, err + } + + var body WriteWarrantResponse + dec := json.NewDecoder(res.Body) + err = dec.Decode(&body) + return body, err +} + +func (c *Client) Check(ctx context.Context, opts CheckOpts) (bool, error) { + return c.CheckMany(ctx, CheckManyOpts{ + Warrants: []WarrantCheck{opts.Warrant}, + Debug: opts.Debug, + WarrantToken: opts.WarrantToken, + }) +} + +func (c *Client) CheckMany(ctx context.Context, opts CheckManyOpts) (bool, error) { + c.once.Do(c.init) + + data, err := c.JSONEncode(opts) + if err != nil { + return false, err + } + + endpoint := fmt.Sprintf("%s/fga/v1/check", c.Endpoint) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(data)) + if err != nil { + return false, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + if opts.WarrantToken != "" { + req.Header.Set("Warrant-Token", opts.WarrantToken) + } + + res, err := c.HTTPClient.Do(req) + if err != nil { + return false, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return false, err + } + + var checkResponse CheckResponse + dec := json.NewDecoder(res.Body) + err = dec.Decode(&checkResponse) + if err != nil { + return false, err + } + + return checkResponse.Result == "Authorized", nil +} + +func (c *Client) BatchCheck(ctx context.Context, opts BatchCheckOpts) ([]bool, error) { + c.once.Do(c.init) + + checkOpts := CheckManyOpts{ + Op: "batch", + Warrants: opts.Warrants, + Debug: opts.Debug, + WarrantToken: opts.WarrantToken, + } + data, err := c.JSONEncode(checkOpts) + if err != nil { + return []bool{}, err + } + + endpoint := fmt.Sprintf("%s/fga/v1/check", c.Endpoint) + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(data)) + if err != nil { + return []bool{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + if opts.WarrantToken != "" { + req.Header.Set("Warrant-Token", opts.WarrantToken) + } + + res, err := c.HTTPClient.Do(req) + if err != nil { + return []bool{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return []bool{}, err + } + + var checkResponses []CheckResponse + dec := json.NewDecoder(res.Body) + err = dec.Decode(&checkResponses) + if err != nil { + return []bool{}, err + } + + var results []bool + for _, checkResponse := range checkResponses { + results = append(results, checkResponse.Result == "Authorized") + } + return results, nil +} + +// Query executes a query for a set of resources. +func (c *Client) Query(ctx context.Context, opts QueryOpts) (QueryResponse, error) { + c.once.Do(c.init) + + endpoint := fmt.Sprintf("%s/fga/v1/query", c.Endpoint) + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return QueryResponse{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + if opts.WarrantToken != "" { + req.Header.Set("Warrant-Token", opts.WarrantToken) + } + + if opts.Limit == 0 { + opts.Limit = ResponseLimit + } + + if opts.Order == "" { + opts.Order = Desc + } + + type QueryUrlOpts struct { + Query string `url:"q"` + Context string `url:"context,omitempty"` + Limit int `url:"limit,omitempty"` + Order Order `url:"order,omitempty"` + Before string `url:"before,omitempty"` + After string `url:"after,omitempty"` + WarrantToken string `url:"-"` + } + + var jsonCtx []byte + if opts.Context != nil { + jsonCtx, err = json.Marshal(opts.Context) + if err != nil { + return QueryResponse{}, err + } + } + queryUrlOpts := QueryUrlOpts{ + Query: opts.Query, + Context: string(jsonCtx), + Limit: opts.Limit, + Order: opts.Order, + Before: opts.Before, + After: opts.After, + } + + q, err := query.Values(queryUrlOpts) + if err != nil { + return QueryResponse{}, err + } + + req.URL.RawQuery = q.Encode() + + res, err := c.HTTPClient.Do(req) + if err != nil { + return QueryResponse{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return QueryResponse{}, err + } + + var body QueryResponse + dec := json.NewDecoder(res.Body) + err = dec.Decode(&body) + return body, err +} diff --git a/pkg/fga/client_live_example.go b/pkg/fga/client_live_example.go new file mode 100644 index 00000000..230f32ef --- /dev/null +++ b/pkg/fga/client_live_example.go @@ -0,0 +1,1860 @@ +package fga + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func setup() { + SetAPIKey("") +} + +func TestCrudObjects(t *testing.T) { + setup() + + object1, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "document", + }) + if err != nil { + t.Fatal(err) + } + require.Equal(t, "document", object1.ObjectType) + require.NotEmpty(t, object1.ObjectId) + require.Empty(t, object1.Meta) + + object2, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "folder", + ObjectId: "planning", + }) + if err != nil { + t.Fatal(err) + } + refetchedObject, err := GetObject(context.Background(), GetObjectOpts{ + ObjectType: object2.ObjectType, + ObjectId: object2.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + require.Equal(t, object2.ObjectType, refetchedObject.ObjectType) + require.Equal(t, object2.ObjectId, refetchedObject.ObjectId) + require.EqualValues(t, object2.Meta, refetchedObject.Meta) + + object2, err = UpdateObject(context.Background(), UpdateObjectOpts{ + ObjectType: object2.ObjectType, + ObjectId: object2.ObjectId, + Meta: map[string]interface{}{ + "description": "Folder object", + }, + }) + if err != nil { + t.Fatal(err) + } + refetchedObject, err = GetObject(context.Background(), GetObjectOpts{ + ObjectType: object2.ObjectType, + ObjectId: object2.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + require.Equal(t, object2.ObjectType, refetchedObject.ObjectType) + require.Equal(t, object2.ObjectId, refetchedObject.ObjectId) + require.EqualValues(t, object2.Meta, refetchedObject.Meta) + + objectsList, err := ListObjects(context.Background(), ListObjectsOpts{ + Limit: 10, + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, objectsList.Data, 2) + require.Equal(t, object2.ObjectType, objectsList.Data[0].ObjectType) + require.Equal(t, object2.ObjectId, objectsList.Data[0].ObjectId) + require.Equal(t, object1.ObjectType, objectsList.Data[1].ObjectType) + require.Equal(t, object1.ObjectId, objectsList.Data[1].ObjectId) + + // Sort in ascending order + objectsList, err = ListObjects(context.Background(), ListObjectsOpts{ + Limit: 10, + Order: Asc, + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, objectsList.Data, 2) + require.Equal(t, object1.ObjectType, objectsList.Data[0].ObjectType) + require.Equal(t, object1.ObjectId, objectsList.Data[0].ObjectId) + require.Equal(t, object2.ObjectType, objectsList.Data[1].ObjectType) + require.Equal(t, object2.ObjectId, objectsList.Data[1].ObjectId) + + objectsList, err = ListObjects(context.Background(), ListObjectsOpts{ + Limit: 10, + Search: "planning", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, objectsList.Data, 1) + require.Equal(t, object2.ObjectType, objectsList.Data[0].ObjectType) + require.Equal(t, object2.ObjectId, objectsList.Data[0].ObjectId) + + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: object1.ObjectType, + ObjectId: object1.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: object2.ObjectType, + ObjectId: object2.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + objectsList, err = ListObjects(context.Background(), ListObjectsOpts{ + Limit: 10, + Search: "planning", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, objectsList.Data, 0) +} + +func TestMultiTenancy(t *testing.T) { + setup() + + // Create users + user1, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + }) + if err != nil { + t.Fatal(err) + } + user2, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + }) + if err != nil { + t.Fatal(err) + } + + // Create tenants + tenant1, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "tenant", + ObjectId: "tenant-1", + Meta: map[string]interface{}{ + "name": "Tenant 1", + }, + }) + if err != nil { + t.Fatal(err) + } + tenant2, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "tenant", + ObjectId: "tenant-2", + Meta: map[string]interface{}{ + "name": "Tenant 2", + }, + }) + if err != nil { + t.Fatal(err) + } + + user1TenantsList, err := Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select tenant where user:%s is member", user1.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, user1TenantsList.Data, 0) + tenant1UsersList, err := Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select member of type user for tenant:%s", tenant1.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, tenant1UsersList.Data, 0) + + // Assign user1 -> tenant1 + warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ + ObjectType: tenant1.ObjectType, + ObjectId: tenant1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + user1TenantsList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select tenant where user:%s is member", user1.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, user1TenantsList.Data, 1) + require.Equal(t, "tenant", user1TenantsList.Data[0].ObjectType) + require.Equal(t, "tenant-1", user1TenantsList.Data[0].ObjectId) + require.EqualValues(t, map[string]interface{}{ + "name": "Tenant 1", + }, user1TenantsList.Data[0].Meta) + + tenant1UsersList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select member of type user for tenant:%s", tenant1.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, tenant1UsersList.Data, 1) + require.Equal(t, "user", tenant1UsersList.Data[0].ObjectType) + require.Equal(t, user1.ObjectId, tenant1UsersList.Data[0].ObjectId) + require.Empty(t, tenant1UsersList.Data[0].Meta) + + // Remove user1 -> tenant1 + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + Op: "delete", + ObjectType: tenant1.ObjectType, + ObjectId: tenant1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + user1TenantsList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select tenant where user:%s is member", user1.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, user1TenantsList.Data, 0) + tenant1UsersList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select member of type user for tenant:%s", tenant1.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, tenant1UsersList.Data, 0) + + // Clean up + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: user2.ObjectType, + ObjectId: user2.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: tenant1.ObjectType, + ObjectId: tenant1.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: tenant2.ObjectType, + ObjectId: tenant2.ObjectId, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestRBAC(t *testing.T) { + setup() + + // Create users + adminUser, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + }) + if err != nil { + t.Fatal(err) + } + viewerUser, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + }) + if err != nil { + t.Fatal(err) + } + + // Create roles + adminRole, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "role", + ObjectId: "administrator", + Meta: map[string]interface{}{ + "name": "Administrator", + "description": "The admin role", + }, + }) + if err != nil { + t.Fatal(err) + } + viewerRole, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "role", + ObjectId: "viewer", + Meta: map[string]interface{}{ + "name": "Viewer", + "description": "The viewer role", + }, + }) + if err != nil { + t.Fatal(err) + } + + // Create permissions + createPermission, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "permission", + ObjectId: "create-report", + Meta: map[string]interface{}{ + "name": "Create Report", + "description": "Permission to create reports", + }, + }) + if err != nil { + t.Fatal(err) + } + viewPermission, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "permission", + ObjectId: "view-report", + Meta: map[string]interface{}{ + "name": "View Report", + "description": "Permission to view reports", + }, + }) + if err != nil { + t.Fatal(err) + } + + adminUserRolesList, err := Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select role where user:%s is member", adminUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, adminUserRolesList.Data, 0) + + adminRolePermissionsList, err := Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select permission where role:%s is member", adminRole.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, adminRolePermissionsList.Data, 0) + + adminUserHasPermission, err := Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: createPermission.ObjectType, + ObjectId: createPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: adminUser.ObjectType, + ObjectId: adminUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, adminUserHasPermission) + + // Assign create-report permission -> admin role -> admin user + warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ + ObjectType: createPermission.ObjectType, + ObjectId: createPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: adminRole.ObjectType, + ObjectId: adminRole.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + ObjectType: adminRole.ObjectType, + ObjectId: adminRole.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: adminUser.ObjectType, + ObjectId: adminUser.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + adminUserHasPermission, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: createPermission.ObjectType, + ObjectId: createPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: adminUser.ObjectType, + ObjectId: adminUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.True(t, adminUserHasPermission) + + adminUserRolesList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select role where user:%s is member", adminUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, adminUserRolesList.Data, 1) + require.Equal(t, "role", adminUserRolesList.Data[0].ObjectType) + require.Equal(t, adminRole.ObjectId, adminUserRolesList.Data[0].ObjectId) + require.Equal(t, map[string]interface{}{ + "name": "Administrator", + "description": "The admin role", + }, adminUserRolesList.Data[0].Meta) + + adminRolePermissionsList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select permission where role:%s is member", adminRole.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, adminRolePermissionsList.Data, 1) + require.Equal(t, "permission", adminRolePermissionsList.Data[0].ObjectType) + require.Equal(t, createPermission.ObjectId, adminRolePermissionsList.Data[0].ObjectId) + require.Equal(t, map[string]interface{}{ + "name": "Create Report", + "description": "Permission to create reports", + }, adminRolePermissionsList.Data[0].Meta) + + // Remove create-report permission -> admin role -> admin user + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + Op: "delete", + ObjectType: createPermission.ObjectType, + ObjectId: createPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: adminRole.ObjectType, + ObjectId: adminRole.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + Op: "delete", + ObjectType: adminRole.ObjectType, + ObjectId: adminRole.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: adminUser.ObjectType, + ObjectId: adminUser.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + adminUserHasPermission, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: createPermission.ObjectType, + ObjectId: createPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: adminUser.ObjectType, + ObjectId: adminUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, adminUserHasPermission) + + adminUserRolesList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select role where user:%s is member", adminUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, adminUserRolesList.Data, 0) + + adminRolePermissionsList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select permission where role:%s is member", adminRole.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, adminRolePermissionsList.Data, 0) + + // Assign view-report -> viewer user + viewerUserHasPermission, err := Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: viewPermission.ObjectType, + ObjectId: viewPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: viewerUser.ObjectType, + ObjectId: viewerUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, viewerUserHasPermission) + + viewerUserPermissionsList, err := Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Empty(t, viewerUserPermissionsList.Data) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + ObjectType: viewPermission.ObjectType, + ObjectId: viewPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: viewerUser.ObjectType, + ObjectId: viewerUser.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + viewerUserHasPermission, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: viewPermission.ObjectType, + ObjectId: viewPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: viewerUser.ObjectType, + ObjectId: viewerUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.True(t, viewerUserHasPermission) + + viewerUserPermissionsList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, viewerUserPermissionsList.Data, 1) + require.Equal(t, "permission", viewerUserPermissionsList.Data[0].ObjectType) + require.Equal(t, viewPermission.ObjectId, viewerUserPermissionsList.Data[0].ObjectId) + require.Equal(t, map[string]interface{}{ + "name": "View Report", + "description": "Permission to view reports", + }, viewerUserPermissionsList.Data[0].Meta) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + Op: "delete", + ObjectType: viewPermission.ObjectType, + ObjectId: viewPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: viewerUser.ObjectType, + ObjectId: viewerUser.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + viewerUserHasPermission, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: viewPermission.ObjectType, + ObjectId: viewPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: viewerUser.ObjectType, + ObjectId: viewerUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, viewerUserHasPermission) + + viewerUserPermissionsList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Empty(t, viewerUserPermissionsList.Data) + + // Clean up + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: adminUser.ObjectType, + ObjectId: adminUser.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: viewerUser.ObjectType, + ObjectId: viewerUser.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: adminRole.ObjectType, + ObjectId: adminRole.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: viewerRole.ObjectType, + ObjectId: viewerRole.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: createPermission.ObjectType, + ObjectId: createPermission.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: viewPermission.ObjectType, + ObjectId: viewPermission.ObjectId, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestPricingTiersFeaturesAndUsers(t *testing.T) { + setup() + + // Create users + freeUser, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + }) + if err != nil { + t.Fatal(err) + } + paidUser, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + }) + if err != nil { + t.Fatal(err) + } + + // Create pricing tiers + freeTier, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "pricing-tier", + ObjectId: "free", + Meta: map[string]interface{}{ + "name": "Free Tier", + }, + }) + if err != nil { + t.Fatal(err) + } + paidTier, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "pricing-tier", + ObjectId: "paid", + }) + if err != nil { + t.Fatal(err) + } + + // Create features + customFeature, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "feature", + ObjectId: "custom", + Meta: map[string]interface{}{ + "name": "Custom Feature", + }, + }) + if err != nil { + t.Fatal(err) + } + feature1, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "feature", + ObjectId: "feature-1", + }) + if err != nil { + t.Fatal(err) + } + feature2, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "feature", + ObjectId: "feature-2", + }) + if err != nil { + t.Fatal(err) + } + + // Assign custom-feature -> paid user + paidUserHasFeature, err := Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: customFeature.ObjectType, + ObjectId: customFeature.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: paidUser.ObjectType, + ObjectId: paidUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, paidUserHasFeature) + + paidUserFeaturesList, err := Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Empty(t, paidUserFeaturesList.Data) + + warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ + ObjectType: customFeature.ObjectType, + ObjectId: customFeature.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: paidUser.ObjectType, + ObjectId: paidUser.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + paidUserHasFeature, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: customFeature.ObjectType, + ObjectId: customFeature.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: paidUser.ObjectType, + ObjectId: paidUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.True(t, paidUserHasFeature) + + paidUserFeaturesList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, paidUserFeaturesList.Data, 1) + require.Equal(t, "feature", paidUserFeaturesList.Data[0].ObjectType) + require.Equal(t, customFeature.ObjectId, paidUserFeaturesList.Data[0].ObjectId) + require.Equal(t, map[string]interface{}{ + "name": "Custom Feature", + }, paidUserFeaturesList.Data[0].Meta) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + Op: "delete", + ObjectType: customFeature.ObjectType, + ObjectId: customFeature.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: paidUser.ObjectType, + ObjectId: paidUser.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + paidUserHasFeature, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: customFeature.ObjectType, + ObjectId: customFeature.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: paidUser.ObjectType, + ObjectId: paidUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, paidUserHasFeature) + + paidUserFeaturesList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Empty(t, paidUserFeaturesList.Data) + + // Assign feature-1 -> free tier -> free user + freeUserHasFeature, err := Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: feature1.ObjectType, + ObjectId: feature1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: freeUser.ObjectType, + ObjectId: freeUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, freeUserHasFeature) + + freeUserFeaturesList, err := Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Empty(t, freeUserFeaturesList.Data) + + featureUserTiersList, err := Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select pricing-tier where user:%s is member", freeUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Empty(t, featureUserTiersList.Data) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + ObjectType: feature1.ObjectType, + ObjectId: feature1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: freeTier.ObjectType, + ObjectId: freeTier.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + ObjectType: freeTier.ObjectType, + ObjectId: freeTier.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: freeUser.ObjectType, + ObjectId: freeUser.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + freeUserHasFeature, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: feature1.ObjectType, + ObjectId: feature1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: freeUser.ObjectType, + ObjectId: freeUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.True(t, freeUserHasFeature) + + freeUserFeaturesList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, freeUserFeaturesList.Data, 1) + require.Equal(t, "feature", freeUserFeaturesList.Data[0].ObjectType) + require.Equal(t, feature1.ObjectId, freeUserFeaturesList.Data[0].ObjectId) + require.Empty(t, freeUserFeaturesList.Data[0].Meta) + + featureUserTiersList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select pricing-tier where user:%s is member", freeUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, featureUserTiersList.Data, 1) + require.Equal(t, "pricing-tier", featureUserTiersList.Data[0].ObjectType) + require.Equal(t, freeTier.ObjectId, featureUserTiersList.Data[0].ObjectId) + require.Equal(t, map[string]interface{}{ + "name": "Free Tier", + }, featureUserTiersList.Data[0].Meta) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + Op: "delete", + ObjectType: feature1.ObjectType, + ObjectId: feature1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: freeTier.ObjectType, + ObjectId: freeTier.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + Op: "delete", + ObjectType: freeTier.ObjectType, + ObjectId: freeTier.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: freeUser.ObjectType, + ObjectId: freeUser.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + freeUserHasFeature, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: feature1.ObjectType, + ObjectId: feature1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: freeUser.ObjectType, + ObjectId: freeUser.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, freeUserHasFeature) + + freeUserFeaturesList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Empty(t, freeUserFeaturesList.Data) + + featureUserTiersList, err = Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select pricing-tier where user:%s is member", freeUser.ObjectId), + Limit: 10, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Empty(t, featureUserTiersList.Data) + + // Clean up + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: freeUser.ObjectType, + ObjectId: freeUser.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: paidUser.ObjectType, + ObjectId: paidUser.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: freeTier.ObjectType, + ObjectId: freeTier.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: paidTier.ObjectType, + ObjectId: paidTier.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: customFeature.ObjectType, + ObjectId: customFeature.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: feature1.ObjectType, + ObjectId: feature1.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: feature2.ObjectType, + ObjectId: feature2.ObjectId, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestWarrants(t *testing.T) { + setup() + + user1, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + ObjectId: "userA", + }) + if err != nil { + t.Fatal(err) + } + user2, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + ObjectId: "userB", + }) + if err != nil { + t.Fatal(err) + } + newPermission, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "permission", + ObjectId: "perm1", + Meta: map[string]interface{}{ + "name": "Permission 1", + "description": "Permission 1", + }, + }) + if err != nil { + t.Fatal(err) + } + + userHasPermission, err := Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: newPermission.ObjectType, + ObjectId: newPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, userHasPermission) + + warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ + ObjectType: newPermission.ObjectType, + ObjectId: newPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + ObjectType: newPermission.ObjectType, + ObjectId: newPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user2.ObjectType, + ObjectId: user2.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + warrants1, err := ListWarrants(context.Background(), ListWarrantsOpts{ + Limit: 1, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, warrants1.Data, 1) + require.Equal(t, newPermission.ObjectType, warrants1.Data[0].ObjectType) + require.Equal(t, newPermission.ObjectId, warrants1.Data[0].ObjectId) + require.Equal(t, "member", warrants1.Data[0].Relation) + require.Equal(t, user2.ObjectType, warrants1.Data[0].Subject.ObjectType) + require.Equal(t, user2.ObjectId, warrants1.Data[0].Subject.ObjectId) + + warrants2, err := ListWarrants(context.Background(), ListWarrantsOpts{ + Limit: 1, + After: warrants1.ListMetadata.After, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, warrants2.Data, 1) + require.Equal(t, newPermission.ObjectType, warrants2.Data[0].ObjectType) + require.Equal(t, newPermission.ObjectId, warrants2.Data[0].ObjectId) + require.Equal(t, "member", warrants2.Data[0].Relation) + require.Equal(t, user1.ObjectType, warrants2.Data[0].Subject.ObjectType) + require.Equal(t, user1.ObjectId, warrants2.Data[0].Subject.ObjectId) + + warrants3, err := ListWarrants(context.Background(), ListWarrantsOpts{ + SubjectType: "user", + SubjectId: user1.ObjectId, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, warrants3.Data, 1) + require.Equal(t, newPermission.ObjectType, warrants3.Data[0].ObjectType) + require.Equal(t, newPermission.ObjectId, warrants3.Data[0].ObjectId) + require.Equal(t, "member", warrants3.Data[0].Relation) + require.Equal(t, user1.ObjectType, warrants3.Data[0].Subject.ObjectType) + require.Equal(t, user1.ObjectId, warrants3.Data[0].Subject.ObjectId) + + userHasPermission, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: newPermission.ObjectType, + ObjectId: newPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.True(t, userHasPermission) + + queryResponse, err := Query(context.Background(), QueryOpts{ + Query: fmt.Sprintf("select permission where user:%s is member", user1.ObjectId), + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, queryResponse.Data, 1) + require.Equal(t, newPermission.ObjectType, queryResponse.Data[0].ObjectType) + require.Equal(t, newPermission.ObjectId, queryResponse.Data[0].ObjectId) + require.Equal(t, "member", queryResponse.Data[0].Relation) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + Op: "delete", + ObjectType: newPermission.ObjectType, + ObjectId: newPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + userHasPermission, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: newPermission.ObjectType, + ObjectId: newPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, userHasPermission) + + // Clean up + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: user2.ObjectType, + ObjectId: user2.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: newPermission.ObjectType, + ObjectId: newPermission.ObjectId, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestBatchWarrants(t *testing.T) { + setup() + + newUser, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + }) + if err != nil { + t.Fatal(err) + } + permission1, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "permission", + ObjectId: "perm1", + Meta: map[string]interface{}{ + "name": "Permission 1", + "description": "Permission 1", + }, + }) + if err != nil { + t.Fatal(err) + } + permission2, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "permission", + ObjectId: "perm2", + Meta: map[string]interface{}{ + "name": "Permission 2", + "description": "Permission 2", + }, + }) + if err != nil { + t.Fatal(err) + } + + userHasPermissions, err := BatchCheck(context.Background(), BatchCheckOpts{ + Warrants: []WarrantCheck{ + { + ObjectType: permission1.ObjectType, + ObjectId: permission1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }, + }, + { + ObjectType: permission2.ObjectType, + ObjectId: permission2.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, userHasPermissions, 2) + require.False(t, userHasPermissions[0]) + require.False(t, userHasPermissions[1]) + + warrantResponse, err := BatchWriteWarrants(context.Background(), []WriteWarrantOpts{ + { + ObjectType: permission1.ObjectType, + ObjectId: permission1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }, + }, + { + Op: "create", + ObjectType: permission2.ObjectType, + ObjectId: permission2.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + userHasPermissions, err = BatchCheck(context.Background(), BatchCheckOpts{ + Warrants: []WarrantCheck{ + { + ObjectType: permission1.ObjectType, + ObjectId: permission1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }, + }, + { + ObjectType: permission2.ObjectType, + ObjectId: permission2.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, userHasPermissions, 2) + require.True(t, userHasPermissions[0]) + require.True(t, userHasPermissions[1]) + + warrantResponse, err = BatchWriteWarrants(context.Background(), []WriteWarrantOpts{ + { + Op: "delete", + ObjectType: permission1.ObjectType, + ObjectId: permission1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }, + }, + { + Op: "delete", + ObjectType: permission2.ObjectType, + ObjectId: permission2.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + userHasPermissions, err = BatchCheck(context.Background(), BatchCheckOpts{ + Warrants: []WarrantCheck{ + { + ObjectType: permission1.ObjectType, + ObjectId: permission1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }, + }, + { + ObjectType: permission2.ObjectType, + ObjectId: permission2.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }, + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, userHasPermissions, 2) + require.False(t, userHasPermissions[0]) + require.False(t, userHasPermissions[1]) + + // Clean up + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: newUser.ObjectType, + ObjectId: newUser.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: permission1.ObjectType, + ObjectId: permission1.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: permission2.ObjectType, + ObjectId: permission2.ObjectId, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestWarrantsWithPolicy(t *testing.T) { + setup() + + warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ + ObjectType: "permission", + ObjectId: "test-permission", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user-1", + }, + Policy: `geo == "us"`, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + checkResult, err := Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: "permission", + ObjectId: "test-permission", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user-1", + }, + Context: map[string]interface{}{ + "geo": "us", + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.True(t, checkResult) + + checkResult, err = Check(context.Background(), CheckOpts{ + Warrant: WarrantCheck{ + ObjectType: "permission", + ObjectId: "test-permission", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user-1", + }, + Context: map[string]interface{}{ + "geo": "eu", + }, + }, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.False(t, checkResult) + + warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ + Op: "delete", + ObjectType: "permission", + ObjectId: "test-permission", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user-1", + }, + Policy: `geo == "us"`, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + // Clean up + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: "permission", + ObjectId: "test-permission", + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: "user", + ObjectId: "user-1", + }) + if err != nil { + t.Fatal(err) + } +} + +func TestQueryWarrants(t *testing.T) { + setup() + + userA, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + ObjectId: "userA", + }) + if err != nil { + t.Fatal(err) + } + userB, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "user", + ObjectId: "userB", + }) + if err != nil { + t.Fatal(err) + } + permission1, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "permission", + ObjectId: "perm1", + Meta: map[string]interface{}{ + "name": "Permission 1", + "description": "This is permission 1.", + }, + }) + if err != nil { + t.Fatal(err) + } + permission2, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "permission", + ObjectId: "perm2", + }) + if err != nil { + t.Fatal(err) + } + permission3, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "permission", + ObjectId: "perm3", + Meta: map[string]interface{}{ + "name": "Permission 3", + "description": "This is permission 3.", + }, + }) + if err != nil { + t.Fatal(err) + } + role1, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "role", + ObjectId: "role1", + Meta: map[string]interface{}{ + "name": "Role 1", + "description": "This is role 1.", + }, + }) + if err != nil { + t.Fatal(err) + } + role2, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "role", + ObjectId: "role2", + Meta: map[string]interface{}{ + "name": "Role 2", + }, + }) + if err != nil { + t.Fatal(err) + } + + warrantResponse, err := BatchWriteWarrants(context.Background(), []WriteWarrantOpts{ + { + ObjectType: permission1.ObjectType, + ObjectId: permission1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: role1.ObjectType, + ObjectId: role1.ObjectId, + }, + }, + { + ObjectType: permission2.ObjectType, + ObjectId: permission2.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: role2.ObjectType, + ObjectId: role2.ObjectId, + }, + }, + { + ObjectType: permission3.ObjectType, + ObjectId: permission3.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: role2.ObjectType, + ObjectId: role2.ObjectId, + }, + }, + { + ObjectType: role2.ObjectType, + ObjectId: role2.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: role1.ObjectType, + ObjectId: role1.ObjectId, + }, + }, + { + ObjectType: permission1.ObjectType, + ObjectId: permission1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: role2.ObjectType, + ObjectId: role2.ObjectId, + }, + Policy: "tenantId == 123", + }, + { + ObjectType: role1.ObjectType, + ObjectId: role1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: userA.ObjectType, + ObjectId: userA.ObjectId, + }, + }, + { + ObjectType: role2.ObjectType, + ObjectId: role2.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: userB.ObjectType, + ObjectId: userB.ObjectId, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + require.NotEmpty(t, warrantResponse.WarrantToken) + + queryResponse, err := Query(context.Background(), QueryOpts{ + Query: "select role where user:userA is member", + Limit: 1, + Order: Asc, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, queryResponse.Data, 1) + require.Equal(t, role1.ObjectType, queryResponse.Data[0].ObjectType) + require.Equal(t, role1.ObjectId, queryResponse.Data[0].ObjectId) + require.Equal(t, "member", queryResponse.Data[0].Relation) + require.Equal(t, role1.ObjectType, queryResponse.Data[0].Warrant.ObjectType) + require.Equal(t, role1.ObjectId, queryResponse.Data[0].Warrant.ObjectId) + require.Equal(t, "member", queryResponse.Data[0].Warrant.Relation) + require.Equal(t, userA.ObjectType, queryResponse.Data[0].Warrant.Subject.ObjectType) + require.Equal(t, userA.ObjectId, queryResponse.Data[0].Warrant.Subject.ObjectId) + require.Empty(t, queryResponse.Data[0].Warrant.Policy) + require.False(t, queryResponse.Data[0].IsImplicit) + + queryResponse, err = Query(context.Background(), QueryOpts{ + Query: "select role where user:userA is member", + Limit: 1, + Order: Asc, + After: queryResponse.ListMetadata.After, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, queryResponse.Data, 1) + require.Equal(t, role2.ObjectType, queryResponse.Data[0].ObjectType) + require.Equal(t, role2.ObjectId, queryResponse.Data[0].ObjectId) + require.Equal(t, "member", queryResponse.Data[0].Relation) + require.Equal(t, role2.ObjectType, queryResponse.Data[0].Warrant.ObjectType) + require.Equal(t, role2.ObjectId, queryResponse.Data[0].Warrant.ObjectId) + require.Equal(t, "member", queryResponse.Data[0].Warrant.Relation) + require.Equal(t, role1.ObjectType, queryResponse.Data[0].Warrant.Subject.ObjectType) + require.Equal(t, role1.ObjectId, queryResponse.Data[0].Warrant.Subject.ObjectId) + require.Empty(t, queryResponse.Data[0].Warrant.Policy) + require.True(t, queryResponse.Data[0].IsImplicit) + + queryResponse, err = Query(context.Background(), QueryOpts{ + Query: "select permission where user:userB is member", + Context: Context{ + "tenantId": 123, + }, + Order: Asc, + WarrantToken: "latest", + }) + if err != nil { + t.Fatal(err) + } + require.Len(t, queryResponse.Data, 3) + require.Equal(t, permission1.ObjectType, queryResponse.Data[0].ObjectType) + require.Equal(t, permission1.ObjectId, queryResponse.Data[0].ObjectId) + require.Equal(t, "member", queryResponse.Data[0].Relation) + require.Equal(t, permission2.ObjectType, queryResponse.Data[1].ObjectType) + require.Equal(t, permission2.ObjectId, queryResponse.Data[1].ObjectId) + require.Equal(t, "member", queryResponse.Data[1].Relation) + require.Equal(t, permission3.ObjectType, queryResponse.Data[2].ObjectType) + require.Equal(t, permission3.ObjectId, queryResponse.Data[2].ObjectId) + require.Equal(t, "member", queryResponse.Data[2].Relation) + + // Clean up + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: role1.ObjectType, + ObjectId: role1.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: role2.ObjectType, + ObjectId: role2.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: permission1.ObjectType, + ObjectId: permission1.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: permission2.ObjectType, + ObjectId: permission2.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: permission3.ObjectType, + ObjectId: permission3.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: userA.ObjectType, + ObjectId: userA.ObjectId, + }) + if err != nil { + t.Fatal(err) + } + err = DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: userB.ObjectType, + ObjectId: userB.ObjectId, + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go new file mode 100644 index 00000000..3b496f94 --- /dev/null +++ b/pkg/fga/client_test.go @@ -0,0 +1,1073 @@ +package fga + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "github.com/workos/workos-go/v4/pkg/common" +) + +func TestGetObject(t *testing.T) { + tests := []struct { + scenario string + client *Client + options GetObjectOpts + expected Object + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns an Object", + client: &Client{ + APIKey: "test", + }, + options: GetObjectOpts{ + ObjectType: "report", + ObjectId: "ljc_1029", + }, + expected: Object{ + ObjectType: "report", + ObjectId: "ljc_1029", + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(getObjectTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + object, err := client.GetObject(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, object) + }) + } +} + +func getObjectTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + body, err := json.Marshal(Object{ + ObjectType: "report", + ObjectId: "ljc_1029", + }) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + +func TestListObjects(t *testing.T) { + tests := []struct { + scenario string + client *Client + options ListObjectsOpts + expected ListObjectsResponse + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns Objects", + client: &Client{ + APIKey: "test", + }, + options: ListObjectsOpts{ + ObjectType: "report", + }, + + expected: ListObjectsResponse{ + Data: []Object{ + { + ObjectType: "report", + ObjectId: "ljc_1029", + }, + { + ObjectType: "report", + ObjectId: "mso_0806", + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(listObjectsTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + objects, err := client.ListObjects(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, objects) + }) + } +} + +func listObjectsTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { + w.WriteHeader(http.StatusBadRequest) + return + } + + body, err := json.Marshal(struct { + ListObjectsResponse + }{ + ListObjectsResponse: ListObjectsResponse{ + Data: []Object{ + { + ObjectType: "report", + ObjectId: "ljc_1029", + }, + { + ObjectType: "report", + ObjectId: "mso_0806", + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + }, + }) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + +func TestCreateObject(t *testing.T) { + tests := []struct { + scenario string + client *Client + options CreateObjectOpts + expected Object + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns Object", + client: &Client{ + APIKey: "test", + }, + options: CreateObjectOpts{ + ObjectType: "report", + ObjectId: "sso_1710", + }, + expected: Object{ + ObjectType: "report", + ObjectId: "sso_1710", + }, + }, + { + scenario: "Request returns Object with Metadata", + client: &Client{ + APIKey: "test", + }, + options: CreateObjectOpts{ + ObjectType: "report", + ObjectId: "sso_1710", + Meta: map[string]interface{}{ + "description": "Some report", + }, + }, + expected: Object{ + ObjectType: "report", + ObjectId: "sso_1710", + Meta: map[string]interface{}{ + "description": "Some report", + }, + }, + }, + { + scenario: "Request with no ObjectId returns an Object with generated report", + client: &Client{ + APIKey: "test", + }, + options: CreateObjectOpts{ + ObjectType: "report", + }, + expected: Object{ + ObjectType: "report", + ObjectId: "report_1029384756", + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(createObjectTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + object, err := client.CreateObject(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, object) + }) + } +} + +func createObjectTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + var opts CreateObjectOpts + json.NewDecoder(r.Body).Decode(&opts) + if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { + w.WriteHeader(http.StatusBadRequest) + return + } + + objectId := "sso_1710" + if opts.ObjectId == "" { + objectId = "report_1029384756" + } + + body, err := json.Marshal( + Object{ + ObjectType: "report", + ObjectId: objectId, + Meta: opts.Meta, + }) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + +func TestUpdateObject(t *testing.T) { + tests := []struct { + scenario string + client *Client + options UpdateObjectOpts + expected Object + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns Object with updated Meta", + client: &Client{ + APIKey: "test", + }, + options: UpdateObjectOpts{ + ObjectType: "report", + ObjectId: "lad_8812", + Meta: map[string]interface{}{ + "description": "Updated report", + }, + }, + expected: Object{ + ObjectType: "report", + ObjectId: "lad_8812", + Meta: map[string]interface{}{ + "description": "Updated report", + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(updateObjectTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + object, err := client.UpdateObject(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, object) + }) + } +} + +func updateObjectTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { + w.WriteHeader(http.StatusBadRequest) + return + } + + body, err := json.Marshal( + Object{ + ObjectType: "report", + ObjectId: "lad_8812", + Meta: map[string]interface{}{ + "description": "Updated report", + }, + }) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + +func TestDeleteObject(t *testing.T) { + tests := []struct { + scenario string + client *Client + options DeleteObjectOpts + expected error + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns Object", + client: &Client{ + APIKey: "test", + }, + options: DeleteObjectOpts{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + expected: nil, + }, + { + scenario: "Request for non-existent Object returns error", + client: &Client{ + APIKey: "test", + }, + err: true, + options: DeleteObjectOpts{ + ObjectType: "user", + ObjectId: "safgdfgs", + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(deleteObjectTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + err := client.DeleteObject(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, err) + }) + } +} + +func deleteObjectTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + var opts CreateObjectOpts + json.NewDecoder(r.Body).Decode(&opts) + + var body []byte + var err error + + if r.URL.Path == "/fga/v1/objects/user/user_01SXW182" { + body, err = nil, nil + } else { + http.Error(w, fmt.Sprintf("%s %s not found", opts.ObjectType, opts.ObjectId), http.StatusNotFound) + return + } + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + +func TestListWarrants(t *testing.T) { + tests := []struct { + scenario string + client *Client + options ListWarrantsOpts + expected ListWarrantsResponse + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns Warrants", + client: &Client{ + APIKey: "test", + }, + options: ListWarrantsOpts{ + ObjectType: "report", + }, + + expected: ListWarrantsResponse{ + Data: []Warrant{ + { + ObjectType: "report", + ObjectId: "ljc_1029", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + { + ObjectType: "report", + ObjectId: "aut_7403", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(listWarrantsTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + objects, err := client.ListWarrants(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, objects) + }) + } +} + +func listWarrantsTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { + w.WriteHeader(http.StatusBadRequest) + return + } + + body, err := json.Marshal(struct { + ListWarrantsResponse + }{ + ListWarrantsResponse: ListWarrantsResponse{ + Data: []Warrant{ + { + ObjectType: "report", + ObjectId: "ljc_1029", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + { + ObjectType: "report", + ObjectId: "aut_7403", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + }, + }) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + +func TestWriteWarrant(t *testing.T) { + tests := []struct { + scenario string + client *Client + options WriteWarrantOpts + expected WriteWarrantResponse + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request with no op returns WarrantToken", + client: &Client{ + APIKey: "test", + }, + options: WriteWarrantOpts{ + ObjectType: "report", + ObjectId: "sso_1710", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + expected: WriteWarrantResponse{ + WarrantToken: "new_warrant_token", + }, + }, + { + scenario: "Request with create op returns WarrantToken", + client: &Client{ + APIKey: "test", + }, + options: WriteWarrantOpts{ + Op: "create", + ObjectType: "report", + ObjectId: "sso_1710", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + expected: WriteWarrantResponse{ + WarrantToken: "new_warrant_token", + }, + }, + { + scenario: "Request with delete op returns WarrantToken", + client: &Client{ + APIKey: "test", + }, + options: WriteWarrantOpts{ + Op: "delete", + ObjectType: "report", + ObjectId: "sso_1710", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + expected: WriteWarrantResponse{ + WarrantToken: "new_warrant_token", + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(writeWarrantTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + warrantResponse, err := client.WriteWarrant(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, warrantResponse) + }) + } +} + +func TestBatchWriteWarrants(t *testing.T) { + tests := []struct { + scenario string + client *Client + options []WriteWarrantOpts + expected WriteWarrantResponse + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request with multiple warrants returns WarrantToken", + client: &Client{ + APIKey: "test", + }, + options: []WriteWarrantOpts{ + { + Op: "delete", + ObjectType: "report", + ObjectId: "sso_1710", + Relation: "viewer", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + { + Op: "create", + ObjectType: "report", + ObjectId: "sso_1710", + Relation: "editor", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + expected: WriteWarrantResponse{ + WarrantToken: "new_warrant_token", + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(writeWarrantTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + warrantResponse, err := client.BatchWriteWarrants(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, warrantResponse) + }) + } +} + +func writeWarrantTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { + w.WriteHeader(http.StatusBadRequest) + return + } + + body, err := json.Marshal( + WriteWarrantResponse{ + WarrantToken: "new_warrant_token", + }) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + +func TestCheckMany(t *testing.T) { + tests := []struct { + scenario string + client *Client + options CheckManyOpts + expected bool + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns true check result", + client: &Client{ + APIKey: "test", + }, + options: CheckManyOpts{ + Warrants: []WarrantCheck{ + { + ObjectType: "report", + ObjectId: "ljc_1029", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + }, + expected: true, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(checkManyTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + checkResult, err := client.CheckMany(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, checkResult) + }) + } +} + +func checkManyTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { + w.WriteHeader(http.StatusBadRequest) + return + } + + body, err := json.Marshal( + CheckResponse{ + Code: 200, + Result: "Authorized", + IsImplicit: false, + }) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + +func TestBatchCheck(t *testing.T) { + tests := []struct { + scenario string + client *Client + options BatchCheckOpts + expected []bool + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns array of check results", + client: &Client{ + APIKey: "test", + }, + options: BatchCheckOpts{ + Warrants: []WarrantCheck{ + { + ObjectType: "report", + ObjectId: "ljc_1029", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + { + ObjectType: "report", + ObjectId: "spt_8521", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + }, + expected: []bool{true, false}, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(batchCheckTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + checkResult, err := client.BatchCheck(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, checkResult) + }) + } +} + +func batchCheckTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { + w.WriteHeader(http.StatusBadRequest) + return + } + + body, err := json.Marshal( + []CheckResponse{ + { + Code: 200, + Result: "Authorized", + IsImplicit: false, + }, + { + Code: 401, + Result: "Not Authorized", + IsImplicit: false, + }, + }) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + +func TestQuery(t *testing.T) { + tests := []struct { + scenario string + client *Client + options QueryOpts + expected QueryResponse + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns QueryResults", + client: &Client{ + APIKey: "test", + }, + options: QueryOpts{ + Query: "select role where user:user_01SXW182 is member", + }, + expected: QueryResponse{ + Data: []QueryResult{ + { + ObjectType: "role", + ObjectId: "role_01SXW182", + Relation: "member", + Warrant: Warrant{ + ObjectType: "role", + ObjectId: "role_01SXW182", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(queryTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + queryResults, err := client.Query(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, queryResults) + }) + } +} + +func queryTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { + w.WriteHeader(http.StatusBadRequest) + return + } + + body, err := json.Marshal(struct { + QueryResponse + }{ + QueryResponse: QueryResponse{ + Data: []QueryResult{ + { + ObjectType: "role", + ObjectId: "role_01SXW182", + Relation: "member", + Warrant: Warrant{ + ObjectType: "role", + ObjectId: "role_01SXW182", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + }, + }) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} diff --git a/pkg/fga/fga.go b/pkg/fga/fga.go new file mode 100644 index 00000000..787e0cfc --- /dev/null +++ b/pkg/fga/fga.go @@ -0,0 +1,111 @@ +package fga + +import "context" + +// DefaultClient is the client used by SetAPIKey and FGA functions. +var ( + DefaultClient = &Client{ + Endpoint: "https://api.workos.com", + } +) + +// SetAPIKey sets the WorkOS API key for FGA requests. +func SetAPIKey(apiKey string) { + DefaultClient.APIKey = apiKey +} + +// GetObject gets an Object. +func GetObject( + ctx context.Context, + opts GetObjectOpts, +) (Object, error) { + return DefaultClient.GetObject(ctx, opts) +} + +// ListObjects gets a list of Objects. +func ListObjects( + ctx context.Context, + opts ListObjectsOpts, +) (ListObjectsResponse, error) { + return DefaultClient.ListObjects(ctx, opts) +} + +// CreateObject creates an Object. +func CreateObject( + ctx context.Context, + opts CreateObjectOpts, +) (Object, error) { + return DefaultClient.CreateObject(ctx, opts) +} + +// UpdateObject updates an Object. +func UpdateObject( + ctx context.Context, + opts UpdateObjectOpts, +) (Object, error) { + return DefaultClient.UpdateObject(ctx, opts) +} + +// DeleteObject deletes an Object. +func DeleteObject( + ctx context.Context, + opts DeleteObjectOpts, +) error { + return DefaultClient.DeleteObject(ctx, opts) +} + +// ListWarrants gets a list of Warrants. +func ListWarrants( + ctx context.Context, + opts ListWarrantsOpts, +) (ListWarrantsResponse, error) { + return DefaultClient.ListWarrants(ctx, opts) +} + +// WriteWarrant performs a write operation on a Warrant. +func WriteWarrant( + ctx context.Context, + opts WriteWarrantOpts, +) (WriteWarrantResponse, error) { + return DefaultClient.WriteWarrant(ctx, opts) +} + +// BatchWriteWarrants performs write operations on multiple Warrants in one request. +func BatchWriteWarrants( + ctx context.Context, + opts []WriteWarrantOpts, +) (WriteWarrantResponse, error) { + return DefaultClient.BatchWriteWarrants(ctx, opts) +} + +// Check performs an access check on a Warrant. +func Check( + ctx context.Context, + opts CheckOpts, +) (bool, error) { + return DefaultClient.Check(ctx, opts) +} + +// CheckMany performs access checks on multiple Warrants. +func CheckMany( + ctx context.Context, + opts CheckManyOpts, +) (bool, error) { + return DefaultClient.CheckMany(ctx, opts) +} + +// BatchCheck performs individual access checks on multiple Warrants in one request. +func BatchCheck( + ctx context.Context, + opts BatchCheckOpts, +) ([]bool, error) { + return DefaultClient.BatchCheck(ctx, opts) +} + +// Query performs a query for a set of resources. +func Query( + ctx context.Context, + opts QueryOpts, +) (QueryResponse, error) { + return DefaultClient.Query(ctx, opts) +} diff --git a/pkg/fga/fga_test.go b/pkg/fga/fga_test.go new file mode 100644 index 00000000..eeb5da69 --- /dev/null +++ b/pkg/fga/fga_test.go @@ -0,0 +1,348 @@ +package fga + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + "github.com/workos/workos-go/v4/pkg/common" +) + +func TestFGAGetObject(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(getObjectTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + expectedResponse := Object{ + ObjectType: "report", + ObjectId: "ljc_1029", + } + objectResponse, err := GetObject(context.Background(), GetObjectOpts{ + ObjectType: "report", + ObjectId: "ljc_1029", + }) + + require.NoError(t, err) + require.Equal(t, expectedResponse, objectResponse) +} + +func TestFGAListObjects(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(listObjectsTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + expectedResponse := ListObjectsResponse{ + Data: []Object{ + { + ObjectType: "report", + ObjectId: "ljc_1029", + }, + { + ObjectType: "report", + ObjectId: "mso_0806", + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + } + objectsResponse, err := ListObjects(context.Background(), ListObjectsOpts{ + ObjectType: "report", + }) + + require.NoError(t, err) + require.Equal(t, expectedResponse, objectsResponse) +} + +func TestFGACreateObject(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(createObjectTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + expectedResponse := Object{ + ObjectType: "report", + ObjectId: "sso_1710", + } + createdObject, err := CreateObject(context.Background(), CreateObjectOpts{ + ObjectType: "report", + ObjectId: "sso_1710", + }) + + require.NoError(t, err) + require.Equal(t, expectedResponse, createdObject) +} + +func TestFGAUpdateObject(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(updateObjectTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + expectedResponse := Object{ + ObjectType: "report", + ObjectId: "lad_8812", + Meta: map[string]interface{}{ + "description": "Updated report", + }, + } + updatedObject, err := UpdateObject(context.Background(), UpdateObjectOpts{ + ObjectType: "report", + ObjectId: "lad_8812", + Meta: map[string]interface{}{ + "description": "Updated report", + }, + }) + + require.NoError(t, err) + require.Equal(t, expectedResponse, updatedObject) +} + +func TestFGADeleteObject(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(deleteObjectTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + err := DeleteObject(context.Background(), DeleteObjectOpts{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }) + + require.NoError(t, err) +} + +func TestFGAListWarrants(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(listWarrantsTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + expectedResponse := ListWarrantsResponse{ + Data: []Warrant{ + { + ObjectType: "report", + ObjectId: "ljc_1029", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + { + ObjectType: "report", + ObjectId: "aut_7403", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + } + warrantsResponse, err := ListWarrants(context.Background(), ListWarrantsOpts{ + ObjectType: "report", + }) + + require.NoError(t, err) + require.Equal(t, expectedResponse, warrantsResponse) +} + +func TestFGAWriteWarrant(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(writeWarrantTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + expectedResponse := WriteWarrantResponse{ + WarrantToken: "new_warrant_token", + } + warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ + Op: "create", + ObjectType: "report", + ObjectId: "sso_1710", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }) + + require.NoError(t, err) + require.Equal(t, expectedResponse, warrantResponse) +} + +func TestFGABatchWriteWarrants(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(writeWarrantTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + expectedResponse := WriteWarrantResponse{ + WarrantToken: "new_warrant_token", + } + warrantResponse, err := BatchWriteWarrants(context.Background(), []WriteWarrantOpts{ + { + Op: "delete", + ObjectType: "report", + ObjectId: "sso_1710", + Relation: "viewer", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + { + Op: "create", + ObjectType: "report", + ObjectId: "sso_1710", + Relation: "editor", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }) + + require.NoError(t, err) + require.Equal(t, expectedResponse, warrantResponse) +} + +func TestFGACheckMany(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(checkManyTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + checkResponse, err := CheckMany(context.Background(), CheckManyOpts{ + Warrants: []WarrantCheck{ + { + ObjectType: "report", + ObjectId: "ljc_1029", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + }) + + require.NoError(t, err) + require.True(t, checkResponse) +} + +func TestFGABatchCheck(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(batchCheckTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + checkResponses, err := BatchCheck(context.Background(), BatchCheckOpts{ + Warrants: []WarrantCheck{ + { + ObjectType: "report", + ObjectId: "ljc_1029", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + }) + + require.NoError(t, err) + require.Len(t, checkResponses, 2) + require.True(t, checkResponses[0]) + require.False(t, checkResponses[1]) +} + +func TestFGAQuery(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(queryTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + expectedResponse := QueryResponse{ + Data: []QueryResult{ + { + ObjectType: "role", + ObjectId: "role_01SXW182", + Relation: "member", + Warrant: Warrant{ + ObjectType: "role", + ObjectId: "role_01SXW182", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user_01SXW182", + }, + }, + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + } + queryResponse, err := Query(context.Background(), QueryOpts{ + Query: "select role where user:user_01SXW182 is member", + }) + + require.NoError(t, err) + require.Equal(t, expectedResponse, queryResponse) +} From 45ce419ead26d1b4f81106fb264cd05c63c3500d Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Thu, 4 Jul 2024 14:07:10 -0700 Subject: [PATCH 03/17] Add object types list and batch update methods --- pkg/fga/client.go | 121 +++++++++++++++++++++ pkg/fga/client_test.go | 235 +++++++++++++++++++++++++++++++++++++++++ pkg/fga/fga.go | 16 +++ pkg/fga/fga_test.go | 93 ++++++++++++++++ 4 files changed, 465 insertions(+) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index 0a9e0272..8903b89c 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -139,6 +139,45 @@ type DeleteObjectOpts struct { ObjectId string } +// Object types +type ObjectType struct { + // Unique string ID of the object type. + Type string `json:"type"` + + // Set of relationships that subjects can have on objects of this type. + Relations map[string]interface{} `json:"relations"` +} + +type ListObjectTypesOpts struct { + // Maximum number of records to return. + Limit int `url:"limit,omitempty"` + + // The order in which to paginate records. + Order Order `url:"order,omitempty"` + + // Pagination cursor to receive records before a provided ObjectType ID. + Before string `url:"before,omitempty"` + + // Pagination cursor to receive records after a provided ObjectType ID. + After string `url:"after,omitempty"` +} + +type ListObjectTypesResponse struct { + // List of Object Types. + Data []ObjectType `json:"data"` + + // Cursor pagination options. + ListMetadata common.ListMetadata `json:"list_metadata"` +} + +type UpdateObjectTypeOpts struct { + // Unique string ID of the object type. + Type string `json:"type"` + + // Set of relationships that subjects can have on objects of this type. + Relations map[string]interface{} `json:"relations"` +} + // Warrants type Subject struct { // The type of the subject. @@ -527,6 +566,88 @@ func (c *Client) DeleteObject(ctx context.Context, opts DeleteObjectOpts) error return workos_errors.TryGetHTTPError(res) } +// ListObjectTypes gets a list of FGA object types. +func (c *Client) ListObjectTypes(ctx context.Context, opts ListObjectTypesOpts) (ListObjectTypesResponse, error) { + c.once.Do(c.init) + + endpoint := fmt.Sprintf("%s/fga/v1/object-types", c.Endpoint) + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return ListObjectTypesResponse{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + + if opts.Limit == 0 { + opts.Limit = ResponseLimit + } + + if opts.Order == "" { + opts.Order = Desc + } + + q, err := query.Values(opts) + if err != nil { + return ListObjectTypesResponse{}, err + } + + req.URL.RawQuery = q.Encode() + + res, err := c.HTTPClient.Do(req) + if err != nil { + return ListObjectTypesResponse{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return ListObjectTypesResponse{}, err + } + + var body ListObjectTypesResponse + dec := json.NewDecoder(res.Body) + err = dec.Decode(&body) + return body, err +} + +// BatchUpdateObjectTypes sets the environment's set of object types to match the object types passed. +func (c *Client) BatchUpdateObjectTypes(ctx context.Context, opts []UpdateObjectTypeOpts) ([]ObjectType, error) { + c.once.Do(c.init) + + data, err := c.JSONEncode(opts) + if err != nil { + return []ObjectType{}, err + } + + endpoint := fmt.Sprintf("%s/fga/v1/object-types", c.Endpoint) + req, err := http.NewRequest(http.MethodPut, endpoint, bytes.NewBuffer(data)) + if err != nil { + return []ObjectType{}, err + } + + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+c.APIKey) + req.Header.Set("User-Agent", "workos-go/"+workos.Version) + + res, err := c.HTTPClient.Do(req) + if err != nil { + return []ObjectType{}, err + } + defer res.Body.Close() + + if err = workos_errors.TryGetHTTPError(res); err != nil { + return []ObjectType{}, err + } + + var body []ObjectType + dec := json.NewDecoder(res.Body) + err = dec.Decode(&body) + return body, err +} + // ListWarrants gets a list of Warrants. func (c *Client) ListWarrants(ctx context.Context, opts ListWarrantsOpts) (ListWarrantsResponse, error) { c.once.Do(c.init) diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go index 3b496f94..b7ed7f3c 100644 --- a/pkg/fga/client_test.go +++ b/pkg/fga/client_test.go @@ -184,6 +184,241 @@ func listObjectsTestHandler(w http.ResponseWriter, r *http.Request) { w.Write(body) } +func TestListObjectTypes(t *testing.T) { + tests := []struct { + scenario string + client *Client + options ListObjectTypesOpts + expected ListObjectTypesResponse + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns ObjectTypes", + client: &Client{ + APIKey: "test", + }, + options: ListObjectTypesOpts{ + Order: "asc", + }, + + expected: ListObjectTypesResponse{ + Data: []ObjectType{ + { + Type: "report", + Relations: map[string]interface{}{ + "owner": map[string]interface{}{}, + "editor": map[string]interface{}{ + "inherit_if": "owner", + }, + "viewer": map[string]interface{}{ + "inherit_if": "editor", + }, + }, + }, + { + Type: "user", + Relations: map[string]interface{}{}, + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(listObjectTypesTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + objectTypes, err := client.ListObjectTypes(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, objectTypes) + }) + } +} + +func listObjectTypesTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { + w.WriteHeader(http.StatusBadRequest) + return + } + + body, err := json.Marshal(struct { + ListObjectTypesResponse + }{ + ListObjectTypesResponse: ListObjectTypesResponse{ + Data: []ObjectType{ + { + Type: "report", + Relations: map[string]interface{}{ + "owner": map[string]interface{}{}, + "editor": map[string]interface{}{ + "inherit_if": "owner", + }, + "viewer": map[string]interface{}{ + "inherit_if": "editor", + }, + }, + }, + { + Type: "user", + Relations: map[string]interface{}{}, + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + }, + }) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + +func TestBatchUpdateObjectTypes(t *testing.T) { + tests := []struct { + scenario string + client *Client + options []UpdateObjectTypeOpts + expected []ObjectType + err bool + }{ + { + scenario: "Request without API Key returns an error", + client: &Client{}, + err: true, + }, + { + scenario: "Request returns ObjectTypes", + client: &Client{ + APIKey: "test", + }, + options: []UpdateObjectTypeOpts{ + { + Type: "report", + Relations: map[string]interface{}{ + "owner": map[string]interface{}{}, + "editor": map[string]interface{}{ + "inherit_if": "owner", + }, + "viewer": map[string]interface{}{ + "inherit_if": "editor", + }, + }, + }, + { + Type: "user", + Relations: map[string]interface{}{}, + }, + }, + + expected: []ObjectType{ + { + Type: "report", + Relations: map[string]interface{}{ + "owner": map[string]interface{}{}, + "editor": map[string]interface{}{ + "inherit_if": "owner", + }, + "viewer": map[string]interface{}{ + "inherit_if": "editor", + }, + }, + }, + { + Type: "user", + Relations: map[string]interface{}{}, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.scenario, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(batchUpdateObjectTypesTestHandler)) + defer server.Close() + + client := test.client + client.Endpoint = server.URL + client.HTTPClient = server.Client() + + objectTypes, err := client.BatchUpdateObjectTypes(context.Background(), test.options) + if test.err { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, test.expected, objectTypes) + }) + } +} + +func batchUpdateObjectTypesTestHandler(w http.ResponseWriter, r *http.Request) { + auth := r.Header.Get("Authorization") + if auth != "Bearer test" { + http.Error(w, "bad auth", http.StatusUnauthorized) + return + } + + if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { + w.WriteHeader(http.StatusBadRequest) + return + } + + body, err := json.Marshal([]ObjectType{ + { + Type: "report", + Relations: map[string]interface{}{ + "owner": map[string]interface{}{}, + "editor": map[string]interface{}{ + "inherit_if": "owner", + }, + "viewer": map[string]interface{}{ + "inherit_if": "editor", + }, + }, + }, + { + Type: "user", + Relations: map[string]interface{}{}, + }, + }) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(body) +} + func TestCreateObject(t *testing.T) { tests := []struct { scenario string diff --git a/pkg/fga/fga.go b/pkg/fga/fga.go index 787e0cfc..0191053b 100644 --- a/pkg/fga/fga.go +++ b/pkg/fga/fga.go @@ -54,6 +54,22 @@ func DeleteObject( return DefaultClient.DeleteObject(ctx, opts) } +// ListObjectTypes gets a list of ObjectTypes. +func ListObjectTypes( + ctx context.Context, + opts ListObjectTypesOpts, +) (ListObjectTypesResponse, error) { + return DefaultClient.ListObjectTypes(ctx, opts) +} + +// BatchUpdateObjectTypes sets the environment's object types to match the provided types. +func BatchUpdateObjectTypes( + ctx context.Context, + opts []UpdateObjectTypeOpts, +) ([]ObjectType, error) { + return DefaultClient.BatchUpdateObjectTypes(ctx, opts) +} + // ListWarrants gets a list of Warrants. func ListWarrants( ctx context.Context, diff --git a/pkg/fga/fga_test.go b/pkg/fga/fga_test.go index eeb5da69..820c9a78 100644 --- a/pkg/fga/fga_test.go +++ b/pkg/fga/fga_test.go @@ -137,6 +137,99 @@ func TestFGADeleteObject(t *testing.T) { require.NoError(t, err) } +func TestFGAListObjectTypes(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(listObjectTypesTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + expectedResponse := ListObjectTypesResponse{ + Data: []ObjectType{ + { + Type: "report", + Relations: map[string]interface{}{ + "owner": map[string]interface{}{}, + "editor": map[string]interface{}{ + "inherit_if": "owner", + }, + "viewer": map[string]interface{}{ + "inherit_if": "editor", + }, + }, + }, + { + Type: "user", + Relations: map[string]interface{}{}, + }, + }, + ListMetadata: common.ListMetadata{ + Before: "", + After: "", + }, + } + objectTypesResponse, err := ListObjectTypes(context.Background(), ListObjectTypesOpts{ + Order: "asc", + }) + + require.NoError(t, err) + require.Equal(t, expectedResponse, objectTypesResponse) +} + +func TestFGABatchUpdateObjectTypes(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(batchUpdateObjectTypesTestHandler)) + defer server.Close() + + DefaultClient = &Client{ + HTTPClient: server.Client(), + Endpoint: server.URL, + } + SetAPIKey("test") + + expectedResponse := []ObjectType{ + { + Type: "report", + Relations: map[string]interface{}{ + "owner": map[string]interface{}{}, + "editor": map[string]interface{}{ + "inherit_if": "owner", + }, + "viewer": map[string]interface{}{ + "inherit_if": "editor", + }, + }, + }, + { + Type: "user", + Relations: map[string]interface{}{}, + }, + } + objectTypes, err := BatchUpdateObjectTypes(context.Background(), []UpdateObjectTypeOpts{ + { + Type: "report", + Relations: map[string]interface{}{ + "owner": map[string]interface{}{}, + "editor": map[string]interface{}{ + "inherit_if": "owner", + }, + "viewer": map[string]interface{}{ + "inherit_if": "editor", + }, + }, + }, + { + Type: "user", + Relations: map[string]interface{}{}, + }, + }) + + require.NoError(t, err) + require.Equal(t, expectedResponse, objectTypes) +} + func TestFGAListWarrants(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(listWarrantsTestHandler)) defer server.Close() From 154b386ff290802d59c6d24208e76fe224efb48c Mon Sep 17 00:00:00 2001 From: Karan Kajla Date: Thu, 4 Jul 2024 18:40:56 -0700 Subject: [PATCH 04/17] Add Authorized method to CheckResponse and return CheckResponse from all check methods --- pkg/fga/client.go | 43 +++++++++++++++---------------- pkg/fga/client_live_example.go | 46 +++++++++++++++++----------------- pkg/fga/fga.go | 6 ++--- 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index 8903b89c..b552b76b 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -23,8 +23,9 @@ type Order string // Constants that enumerate the available orders. const ( - Asc Order = "asc" - Desc Order = "desc" + CheckResultAuthorized = "Authorized" + Asc Order = "asc" + Desc Order = "desc" ) // Client represents a client that performs FGA requests to the WorkOS API. @@ -333,6 +334,10 @@ type CheckResponse struct { DecisionPath map[string][]Warrant `json:"decision_path,omitempty"` } +func (checkResponse CheckResponse) Authorized() bool { + return checkResponse.Result == CheckResultAuthorized +} + // Query type QueryOpts struct { // Query to be executed. @@ -765,7 +770,7 @@ func (c *Client) BatchWriteWarrants(ctx context.Context, opts []WriteWarrantOpts return body, err } -func (c *Client) Check(ctx context.Context, opts CheckOpts) (bool, error) { +func (c *Client) Check(ctx context.Context, opts CheckOpts) (CheckResponse, error) { return c.CheckMany(ctx, CheckManyOpts{ Warrants: []WarrantCheck{opts.Warrant}, Debug: opts.Debug, @@ -773,18 +778,18 @@ func (c *Client) Check(ctx context.Context, opts CheckOpts) (bool, error) { }) } -func (c *Client) CheckMany(ctx context.Context, opts CheckManyOpts) (bool, error) { +func (c *Client) CheckMany(ctx context.Context, opts CheckManyOpts) (CheckResponse, error) { c.once.Do(c.init) data, err := c.JSONEncode(opts) if err != nil { - return false, err + return CheckResponse{}, err } endpoint := fmt.Sprintf("%s/fga/v1/check", c.Endpoint) req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(data)) if err != nil { - return false, err + return CheckResponse{}, err } req = req.WithContext(ctx) @@ -797,25 +802,25 @@ func (c *Client) CheckMany(ctx context.Context, opts CheckManyOpts) (bool, error res, err := c.HTTPClient.Do(req) if err != nil { - return false, err + return CheckResponse{}, err } defer res.Body.Close() if err = workos_errors.TryGetHTTPError(res); err != nil { - return false, err + return CheckResponse{}, err } var checkResponse CheckResponse dec := json.NewDecoder(res.Body) err = dec.Decode(&checkResponse) if err != nil { - return false, err + return CheckResponse{}, err } - return checkResponse.Result == "Authorized", nil + return checkResponse, nil } -func (c *Client) BatchCheck(ctx context.Context, opts BatchCheckOpts) ([]bool, error) { +func (c *Client) BatchCheck(ctx context.Context, opts BatchCheckOpts) ([]CheckResponse, error) { c.once.Do(c.init) checkOpts := CheckManyOpts{ @@ -826,13 +831,13 @@ func (c *Client) BatchCheck(ctx context.Context, opts BatchCheckOpts) ([]bool, e } data, err := c.JSONEncode(checkOpts) if err != nil { - return []bool{}, err + return []CheckResponse{}, err } endpoint := fmt.Sprintf("%s/fga/v1/check", c.Endpoint) req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(data)) if err != nil { - return []bool{}, err + return []CheckResponse{}, err } req = req.WithContext(ctx) @@ -845,26 +850,22 @@ func (c *Client) BatchCheck(ctx context.Context, opts BatchCheckOpts) ([]bool, e res, err := c.HTTPClient.Do(req) if err != nil { - return []bool{}, err + return []CheckResponse{}, err } defer res.Body.Close() if err = workos_errors.TryGetHTTPError(res); err != nil { - return []bool{}, err + return []CheckResponse{}, err } var checkResponses []CheckResponse dec := json.NewDecoder(res.Body) err = dec.Decode(&checkResponses) if err != nil { - return []bool{}, err + return []CheckResponse{}, err } - var results []bool - for _, checkResponse := range checkResponses { - results = append(results, checkResponse.Result == "Authorized") - } - return results, nil + return checkResponses, nil } // Query executes a query for a set of resources. diff --git a/pkg/fga/client_live_example.go b/pkg/fga/client_live_example.go index 230f32ef..a2d6583e 100644 --- a/pkg/fga/client_live_example.go +++ b/pkg/fga/client_live_example.go @@ -392,7 +392,7 @@ func TestRBAC(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, adminUserHasPermission) + require.False(t, adminUserHasPermission.Authorized()) // Assign create-report permission -> admin role -> admin user warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ @@ -438,7 +438,7 @@ func TestRBAC(t *testing.T) { if err != nil { t.Fatal(err) } - require.True(t, adminUserHasPermission) + require.True(t, adminUserHasPermission.Authorized()) adminUserRolesList, err = Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select role where user:%s is member", adminUser.ObjectId), @@ -518,7 +518,7 @@ func TestRBAC(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, adminUserHasPermission) + require.False(t, adminUserHasPermission.Authorized()) adminUserRolesList, err = Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select role where user:%s is member", adminUser.ObjectId), @@ -556,7 +556,7 @@ func TestRBAC(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, viewerUserHasPermission) + require.False(t, viewerUserHasPermission.Authorized()) viewerUserPermissionsList, err := Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ObjectId), @@ -597,7 +597,7 @@ func TestRBAC(t *testing.T) { if err != nil { t.Fatal(err) } - require.True(t, viewerUserHasPermission) + require.True(t, viewerUserHasPermission.Authorized()) viewerUserPermissionsList, err = Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ObjectId), @@ -645,7 +645,7 @@ func TestRBAC(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, viewerUserHasPermission) + require.False(t, viewerUserHasPermission.Authorized()) viewerUserPermissionsList, err = Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ObjectId), @@ -780,7 +780,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, paidUserHasFeature) + require.False(t, paidUserHasFeature.Authorized()) paidUserFeaturesList, err := Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ObjectId), @@ -821,7 +821,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { if err != nil { t.Fatal(err) } - require.True(t, paidUserHasFeature) + require.True(t, paidUserHasFeature.Authorized()) paidUserFeaturesList, err = Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ObjectId), @@ -868,7 +868,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, paidUserHasFeature) + require.False(t, paidUserHasFeature.Authorized()) paidUserFeaturesList, err = Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ObjectId), @@ -896,7 +896,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, freeUserHasFeature) + require.False(t, freeUserHasFeature.Authorized()) freeUserFeaturesList, err := Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ObjectId), @@ -961,7 +961,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { if err != nil { t.Fatal(err) } - require.True(t, freeUserHasFeature) + require.True(t, freeUserHasFeature.Authorized()) freeUserFeaturesList, err = Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ObjectId), @@ -1036,7 +1036,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, freeUserHasFeature) + require.False(t, freeUserHasFeature.Authorized()) freeUserFeaturesList, err = Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ObjectId), @@ -1154,7 +1154,7 @@ func TestWarrants(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, userHasPermission) + require.False(t, userHasPermission.Authorized()) warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ ObjectType: newPermission.ObjectType, @@ -1243,7 +1243,7 @@ func TestWarrants(t *testing.T) { if err != nil { t.Fatal(err) } - require.True(t, userHasPermission) + require.True(t, userHasPermission.Authorized()) queryResponse, err := Query(context.Background(), QueryOpts{ Query: fmt.Sprintf("select permission where user:%s is member", user1.ObjectId), @@ -1287,7 +1287,7 @@ func TestWarrants(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, userHasPermission) + require.False(t, userHasPermission.Authorized()) // Clean up err = DeleteObject(context.Background(), DeleteObjectOpts{ @@ -1372,8 +1372,8 @@ func TestBatchWarrants(t *testing.T) { t.Fatal(err) } require.Len(t, userHasPermissions, 2) - require.False(t, userHasPermissions[0]) - require.False(t, userHasPermissions[1]) + require.False(t, userHasPermissions[0].Authorized()) + require.False(t, userHasPermissions[1].Authorized()) warrantResponse, err := BatchWriteWarrants(context.Background(), []WriteWarrantOpts{ { @@ -1428,8 +1428,8 @@ func TestBatchWarrants(t *testing.T) { t.Fatal(err) } require.Len(t, userHasPermissions, 2) - require.True(t, userHasPermissions[0]) - require.True(t, userHasPermissions[1]) + require.True(t, userHasPermissions[0].Authorized()) + require.True(t, userHasPermissions[1].Authorized()) warrantResponse, err = BatchWriteWarrants(context.Background(), []WriteWarrantOpts{ { @@ -1485,8 +1485,8 @@ func TestBatchWarrants(t *testing.T) { t.Fatal(err) } require.Len(t, userHasPermissions, 2) - require.False(t, userHasPermissions[0]) - require.False(t, userHasPermissions[1]) + require.False(t, userHasPermissions[0].Authorized()) + require.False(t, userHasPermissions[1].Authorized()) // Clean up err = DeleteObject(context.Background(), DeleteObjectOpts{ @@ -1548,7 +1548,7 @@ func TestWarrantsWithPolicy(t *testing.T) { if err != nil { t.Fatal(err) } - require.True(t, checkResult) + require.True(t, checkResult.Authorized()) checkResult, err = Check(context.Background(), CheckOpts{ Warrant: WarrantCheck{ @@ -1568,7 +1568,7 @@ func TestWarrantsWithPolicy(t *testing.T) { if err != nil { t.Fatal(err) } - require.False(t, checkResult) + require.False(t, checkResult.Authorized()) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ Op: "delete", diff --git a/pkg/fga/fga.go b/pkg/fga/fga.go index 0191053b..0e052cee 100644 --- a/pkg/fga/fga.go +++ b/pkg/fga/fga.go @@ -98,7 +98,7 @@ func BatchWriteWarrants( func Check( ctx context.Context, opts CheckOpts, -) (bool, error) { +) (CheckResponse, error) { return DefaultClient.Check(ctx, opts) } @@ -106,7 +106,7 @@ func Check( func CheckMany( ctx context.Context, opts CheckManyOpts, -) (bool, error) { +) (CheckResponse, error) { return DefaultClient.CheckMany(ctx, opts) } @@ -114,7 +114,7 @@ func CheckMany( func BatchCheck( ctx context.Context, opts BatchCheckOpts, -) ([]bool, error) { +) ([]CheckResponse, error) { return DefaultClient.BatchCheck(ctx, opts) } From 871d75118edf7725925322fc440f07c9ad60d659 Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Tue, 9 Jul 2024 16:50:27 -0700 Subject: [PATCH 05/17] Make ObjectId required for UpdateObjectOpts --- pkg/fga/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index b552b76b..706a6475 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -125,7 +125,7 @@ type UpdateObjectOpts struct { ObjectType string `json:"object_type"` // The customer defined string identifier for this object. - ObjectId string `json:"object_id,omitempty"` + ObjectId string `json:"object_id"` // Map containing additional information about this object. Meta map[string]interface{} `json:"meta,omitempty"` From 249710871ce0ca1567318744c2a9ae91972d24ce Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Wed, 10 Jul 2024 12:52:17 -0700 Subject: [PATCH 06/17] Update FGA tests --- pkg/fga/client_test.go | 29 ++++++++++++++++++++++------- pkg/fga/fga_test.go | 6 +++--- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go index b7ed7f3c..9aac6884 100644 --- a/pkg/fga/client_test.go +++ b/pkg/fga/client_test.go @@ -1017,7 +1017,7 @@ func TestCheckMany(t *testing.T) { scenario string client *Client options CheckManyOpts - expected bool + expected CheckResponse err bool }{ { @@ -1043,7 +1043,11 @@ func TestCheckMany(t *testing.T) { }, }, }, - expected: true, + expected: CheckResponse{ + Code: 200, + Result: "Authorized", + IsImplicit: false, + }, }, } @@ -1100,7 +1104,7 @@ func TestBatchCheck(t *testing.T) { scenario string client *Client options BatchCheckOpts - expected []bool + expected []CheckResponse err bool }{ { @@ -1135,7 +1139,18 @@ func TestBatchCheck(t *testing.T) { }, }, }, - expected: []bool{true, false}, + expected: []CheckResponse{ + { + Code: 200, + Result: "Authorized", + IsImplicit: false, + }, + { + Code: 403, + Result: "Not Authorized", + IsImplicit: false, + }, + }, }, } @@ -1148,13 +1163,13 @@ func TestBatchCheck(t *testing.T) { client.Endpoint = server.URL client.HTTPClient = server.Client() - checkResult, err := client.BatchCheck(context.Background(), test.options) + checkResults, err := client.BatchCheck(context.Background(), test.options) if test.err { require.Error(t, err) return } require.NoError(t, err) - require.Equal(t, test.expected, checkResult) + require.Equal(t, test.expected, checkResults) }) } } @@ -1179,7 +1194,7 @@ func batchCheckTestHandler(w http.ResponseWriter, r *http.Request) { IsImplicit: false, }, { - Code: 401, + Code: 403, Result: "Not Authorized", IsImplicit: false, }, diff --git a/pkg/fga/fga_test.go b/pkg/fga/fga_test.go index 820c9a78..59407c06 100644 --- a/pkg/fga/fga_test.go +++ b/pkg/fga/fga_test.go @@ -367,7 +367,7 @@ func TestFGACheckMany(t *testing.T) { }) require.NoError(t, err) - require.True(t, checkResponse) + require.True(t, checkResponse.Authorized()) } func TestFGABatchCheck(t *testing.T) { @@ -396,8 +396,8 @@ func TestFGABatchCheck(t *testing.T) { require.NoError(t, err) require.Len(t, checkResponses, 2) - require.True(t, checkResponses[0]) - require.False(t, checkResponses[1]) + require.True(t, checkResponses[0].Authorized()) + require.False(t, checkResponses[1].Authorized()) } func TestFGAQuery(t *testing.T) { From 89a1fdcc2a73738822a1534d128e4197427d947b Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Mon, 15 Jul 2024 11:43:50 -0700 Subject: [PATCH 07/17] Rename Warrants field to Checks --- pkg/fga/client.go | 8 ++++---- pkg/fga/client_test.go | 2 +- pkg/fga/fga_test.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index 706a6475..e9c45456 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -33,7 +33,7 @@ type Client struct { // The WorkOS API Key. It can be found in https://dashboard.workos.com/api-keys. APIKey string - // The http.Client that is used to get Directory Sync records from WorkOS. + // The http.Client that is used to get FGA records from WorkOS. // Defaults to http.Client. HTTPClient *http.Client @@ -306,7 +306,7 @@ type CheckManyOpts struct { Op string `json:"op,omitempty"` // List of warrants to check. - Warrants []WarrantCheck `json:"warrants"` + Checks []WarrantCheck `json:"checks"` // Flag to include debug information in the response. Debug bool `json:"debug,omitempty"` @@ -772,7 +772,7 @@ func (c *Client) BatchWriteWarrants(ctx context.Context, opts []WriteWarrantOpts func (c *Client) Check(ctx context.Context, opts CheckOpts) (CheckResponse, error) { return c.CheckMany(ctx, CheckManyOpts{ - Warrants: []WarrantCheck{opts.Warrant}, + Checks: []WarrantCheck{opts.Warrant}, Debug: opts.Debug, WarrantToken: opts.WarrantToken, }) @@ -825,7 +825,7 @@ func (c *Client) BatchCheck(ctx context.Context, opts BatchCheckOpts) ([]CheckRe checkOpts := CheckManyOpts{ Op: "batch", - Warrants: opts.Warrants, + Checks: opts.Warrants, Debug: opts.Debug, WarrantToken: opts.WarrantToken, } diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go index 9aac6884..21487155 100644 --- a/pkg/fga/client_test.go +++ b/pkg/fga/client_test.go @@ -1031,7 +1031,7 @@ func TestCheckMany(t *testing.T) { APIKey: "test", }, options: CheckManyOpts{ - Warrants: []WarrantCheck{ + Checks: []WarrantCheck{ { ObjectType: "report", ObjectId: "ljc_1029", diff --git a/pkg/fga/fga_test.go b/pkg/fga/fga_test.go index 59407c06..d710fd54 100644 --- a/pkg/fga/fga_test.go +++ b/pkg/fga/fga_test.go @@ -353,7 +353,7 @@ func TestFGACheckMany(t *testing.T) { SetAPIKey("test") checkResponse, err := CheckMany(context.Background(), CheckManyOpts{ - Warrants: []WarrantCheck{ + Checks: []WarrantCheck{ { ObjectType: "report", ObjectId: "ljc_1029", From aa3f120bbf44c88e8eff3c69b9c14037672dd725 Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Mon, 15 Jul 2024 12:09:30 -0700 Subject: [PATCH 08/17] Update CheckResponse to include DebugInfo struct --- pkg/fga/client.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index e9c45456..e6cf045f 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -327,17 +327,29 @@ type BatchCheckOpts struct { } type CheckResponse struct { - Code int64 `json:"code"` - Result string `json:"result"` - IsImplicit bool `json:"is_implicit"` - ProcessingTime int64 `json:"processing_time,omitempty"` - DecisionPath map[string][]Warrant `json:"decision_path,omitempty"` + Code int64 `json:"code"` + Result string `json:"result"` + IsImplicit bool `json:"is_implicit"` + DebugInfo DebugInfo `json:"debug_info,omitempty"` } func (checkResponse CheckResponse) Authorized() bool { return checkResponse.Result == CheckResultAuthorized } +type DebugInfo struct { + ProcessingTime time.Duration `json:"processing_time"` + DecisionTree *DecisionTreeNode `json:"decision_tree"` +} + +type DecisionTreeNode struct { + Check WarrantCheck `json:"check"` + Policy string `json:"policy,omitempty"` + Decision string `json:"decision"` + ProcessingTime time.Duration `json:"processing_time"` + Children []DecisionTreeNode `json:"children"` +} + // Query type QueryOpts struct { // Query to be executed. From b31d2a4f8394bfcea2bcdff860e5cda2623f6625 Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Mon, 15 Jul 2024 13:12:02 -0700 Subject: [PATCH 09/17] Rename BatchCheck to CheckBatch --- pkg/fga/client.go | 4 ++-- pkg/fga/client_live_example.go | 6 +++--- pkg/fga/client_test.go | 12 ++++++------ pkg/fga/fga.go | 4 ++-- pkg/fga/fga_test.go | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index e6cf045f..dd1e8bdf 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -315,7 +315,7 @@ type CheckManyOpts struct { WarrantToken string `json:"-"` } -type BatchCheckOpts struct { +type CheckBatchOpts struct { // List of warrants to check. Warrants []WarrantCheck `json:"warrants"` @@ -832,7 +832,7 @@ func (c *Client) CheckMany(ctx context.Context, opts CheckManyOpts) (CheckRespon return checkResponse, nil } -func (c *Client) BatchCheck(ctx context.Context, opts BatchCheckOpts) ([]CheckResponse, error) { +func (c *Client) CheckBatch(ctx context.Context, opts CheckBatchOpts) ([]CheckResponse, error) { c.once.Do(c.init) checkOpts := CheckManyOpts{ diff --git a/pkg/fga/client_live_example.go b/pkg/fga/client_live_example.go index a2d6583e..3dc25fb1 100644 --- a/pkg/fga/client_live_example.go +++ b/pkg/fga/client_live_example.go @@ -1345,7 +1345,7 @@ func TestBatchWarrants(t *testing.T) { t.Fatal(err) } - userHasPermissions, err := BatchCheck(context.Background(), BatchCheckOpts{ + userHasPermissions, err := BatchCheck(context.Background(), CheckBatchOpts{ Warrants: []WarrantCheck{ { ObjectType: permission1.ObjectType, @@ -1401,7 +1401,7 @@ func TestBatchWarrants(t *testing.T) { } require.NotEmpty(t, warrantResponse.WarrantToken) - userHasPermissions, err = BatchCheck(context.Background(), BatchCheckOpts{ + userHasPermissions, err = BatchCheck(context.Background(), CheckBatchOpts{ Warrants: []WarrantCheck{ { ObjectType: permission1.ObjectType, @@ -1458,7 +1458,7 @@ func TestBatchWarrants(t *testing.T) { } require.NotEmpty(t, warrantResponse.WarrantToken) - userHasPermissions, err = BatchCheck(context.Background(), BatchCheckOpts{ + userHasPermissions, err = BatchCheck(context.Background(), CheckBatchOpts{ Warrants: []WarrantCheck{ { ObjectType: permission1.ObjectType, diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go index 21487155..0fcffa49 100644 --- a/pkg/fga/client_test.go +++ b/pkg/fga/client_test.go @@ -1099,11 +1099,11 @@ func checkManyTestHandler(w http.ResponseWriter, r *http.Request) { w.Write(body) } -func TestBatchCheck(t *testing.T) { +func TestCheckBatch(t *testing.T) { tests := []struct { scenario string client *Client - options BatchCheckOpts + options CheckBatchOpts expected []CheckResponse err bool }{ @@ -1117,7 +1117,7 @@ func TestBatchCheck(t *testing.T) { client: &Client{ APIKey: "test", }, - options: BatchCheckOpts{ + options: CheckBatchOpts{ Warrants: []WarrantCheck{ { ObjectType: "report", @@ -1156,14 +1156,14 @@ func TestBatchCheck(t *testing.T) { for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(batchCheckTestHandler)) + server := httptest.NewServer(http.HandlerFunc(checkBatchTestHandler)) defer server.Close() client := test.client client.Endpoint = server.URL client.HTTPClient = server.Client() - checkResults, err := client.BatchCheck(context.Background(), test.options) + checkResults, err := client.CheckBatch(context.Background(), test.options) if test.err { require.Error(t, err) return @@ -1174,7 +1174,7 @@ func TestBatchCheck(t *testing.T) { } } -func batchCheckTestHandler(w http.ResponseWriter, r *http.Request) { +func checkBatchTestHandler(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if auth != "Bearer test" { http.Error(w, "bad auth", http.StatusUnauthorized) diff --git a/pkg/fga/fga.go b/pkg/fga/fga.go index 0e052cee..285b51ab 100644 --- a/pkg/fga/fga.go +++ b/pkg/fga/fga.go @@ -113,9 +113,9 @@ func CheckMany( // BatchCheck performs individual access checks on multiple Warrants in one request. func BatchCheck( ctx context.Context, - opts BatchCheckOpts, + opts CheckBatchOpts, ) ([]CheckResponse, error) { - return DefaultClient.BatchCheck(ctx, opts) + return DefaultClient.CheckBatch(ctx, opts) } // Query performs a query for a set of resources. diff --git a/pkg/fga/fga_test.go b/pkg/fga/fga_test.go index d710fd54..e14c50d0 100644 --- a/pkg/fga/fga_test.go +++ b/pkg/fga/fga_test.go @@ -380,7 +380,7 @@ func TestFGABatchCheck(t *testing.T) { } SetAPIKey("test") - checkResponses, err := BatchCheck(context.Background(), BatchCheckOpts{ + checkResponses, err := BatchCheck(context.Background(), CheckBatchOpts{ Warrants: []WarrantCheck{ { ObjectType: "report", From 280f5d9136ac2f3d2c7ca132f00a04857febbbd8 Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Mon, 15 Jul 2024 13:15:16 -0700 Subject: [PATCH 10/17] Remove single check method and rename CheckMany to Check --- pkg/fga/client.go | 21 +-------------------- pkg/fga/client_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index dd1e8bdf..6dfcb223 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -291,17 +291,6 @@ type WarrantCheck struct { } type CheckOpts struct { - // Warrant to check - Warrant WarrantCheck `json:"warrant_check"` - - // Flag to include debug information in the response. - Debug bool `json:"debug,omitempty"` - - // Optional token to specify desired read consistency - WarrantToken string `json:"-"` -} - -type CheckManyOpts struct { // The operator to use for the given warrants. Op string `json:"op,omitempty"` @@ -783,14 +772,6 @@ func (c *Client) BatchWriteWarrants(ctx context.Context, opts []WriteWarrantOpts } func (c *Client) Check(ctx context.Context, opts CheckOpts) (CheckResponse, error) { - return c.CheckMany(ctx, CheckManyOpts{ - Checks: []WarrantCheck{opts.Warrant}, - Debug: opts.Debug, - WarrantToken: opts.WarrantToken, - }) -} - -func (c *Client) CheckMany(ctx context.Context, opts CheckManyOpts) (CheckResponse, error) { c.once.Do(c.init) data, err := c.JSONEncode(opts) @@ -835,7 +816,7 @@ func (c *Client) CheckMany(ctx context.Context, opts CheckManyOpts) (CheckRespon func (c *Client) CheckBatch(ctx context.Context, opts CheckBatchOpts) ([]CheckResponse, error) { c.once.Do(c.init) - checkOpts := CheckManyOpts{ + checkOpts := CheckOpts{ Op: "batch", Checks: opts.Warrants, Debug: opts.Debug, diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go index 0fcffa49..7f509db5 100644 --- a/pkg/fga/client_test.go +++ b/pkg/fga/client_test.go @@ -1012,11 +1012,11 @@ func writeWarrantTestHandler(w http.ResponseWriter, r *http.Request) { w.Write(body) } -func TestCheckMany(t *testing.T) { +func TestCheck(t *testing.T) { tests := []struct { scenario string client *Client - options CheckManyOpts + options CheckOpts expected CheckResponse err bool }{ @@ -1030,7 +1030,7 @@ func TestCheckMany(t *testing.T) { client: &Client{ APIKey: "test", }, - options: CheckManyOpts{ + options: CheckOpts{ Checks: []WarrantCheck{ { ObjectType: "report", @@ -1053,14 +1053,14 @@ func TestCheckMany(t *testing.T) { for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(checkManyTestHandler)) + server := httptest.NewServer(http.HandlerFunc(checkTestHandler)) defer server.Close() client := test.client client.Endpoint = server.URL client.HTTPClient = server.Client() - checkResult, err := client.CheckMany(context.Background(), test.options) + checkResult, err := client.Check(context.Background(), test.options) if test.err { require.Error(t, err) return @@ -1071,7 +1071,7 @@ func TestCheckMany(t *testing.T) { } } -func checkManyTestHandler(w http.ResponseWriter, r *http.Request) { +func checkTestHandler(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if auth != "Bearer test" { http.Error(w, "bad auth", http.StatusUnauthorized) From 5894c8113f229b3a86c5d547fe2c0f7941666c4e Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Mon, 15 Jul 2024 14:10:32 -0700 Subject: [PATCH 11/17] Rename Warrants field to Checks --- pkg/fga/client.go | 4 +- pkg/fga/client_live_example.go | 296 ++++++++++++++++++--------------- pkg/fga/client_test.go | 2 +- pkg/fga/fga.go | 14 +- pkg/fga/fga_test.go | 14 +- 5 files changed, 178 insertions(+), 152 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index 6dfcb223..524d7e8c 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -306,7 +306,7 @@ type CheckOpts struct { type CheckBatchOpts struct { // List of warrants to check. - Warrants []WarrantCheck `json:"warrants"` + Checks []WarrantCheck `json:"warrants"` // Flag to include debug information in the response. Debug bool `json:"debug,omitempty"` @@ -818,7 +818,7 @@ func (c *Client) CheckBatch(ctx context.Context, opts CheckBatchOpts) ([]CheckRe checkOpts := CheckOpts{ Op: "batch", - Checks: opts.Warrants, + Checks: opts.Checks, Debug: opts.Debug, WarrantToken: opts.WarrantToken, } diff --git a/pkg/fga/client_live_example.go b/pkg/fga/client_live_example.go index 3dc25fb1..53de7531 100644 --- a/pkg/fga/client_live_example.go +++ b/pkg/fga/client_live_example.go @@ -378,13 +378,15 @@ func TestRBAC(t *testing.T) { require.Len(t, adminRolePermissionsList.Data, 0) adminUserHasPermission, err := Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: createPermission.ObjectType, - ObjectId: createPermission.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: adminUser.ObjectType, - ObjectId: adminUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: createPermission.ObjectType, + ObjectId: createPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: adminUser.ObjectType, + ObjectId: adminUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -424,13 +426,15 @@ func TestRBAC(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) adminUserHasPermission, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: createPermission.ObjectType, - ObjectId: createPermission.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: adminUser.ObjectType, - ObjectId: adminUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: createPermission.ObjectType, + ObjectId: createPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: adminUser.ObjectType, + ObjectId: adminUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -504,13 +508,15 @@ func TestRBAC(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) adminUserHasPermission, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: createPermission.ObjectType, - ObjectId: createPermission.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: adminUser.ObjectType, - ObjectId: adminUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: createPermission.ObjectType, + ObjectId: createPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: adminUser.ObjectType, + ObjectId: adminUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -542,13 +548,15 @@ func TestRBAC(t *testing.T) { // Assign view-report -> viewer user viewerUserHasPermission, err := Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: viewPermission.ObjectType, - ObjectId: viewPermission.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: viewerUser.ObjectType, - ObjectId: viewerUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: viewPermission.ObjectType, + ObjectId: viewPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: viewerUser.ObjectType, + ObjectId: viewerUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -583,13 +591,15 @@ func TestRBAC(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) viewerUserHasPermission, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: viewPermission.ObjectType, - ObjectId: viewPermission.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: viewerUser.ObjectType, - ObjectId: viewerUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: viewPermission.ObjectType, + ObjectId: viewPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: viewerUser.ObjectType, + ObjectId: viewerUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -631,13 +641,15 @@ func TestRBAC(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) viewerUserHasPermission, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: viewPermission.ObjectType, - ObjectId: viewPermission.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: viewerUser.ObjectType, - ObjectId: viewerUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: viewPermission.ObjectType, + ObjectId: viewPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: viewerUser.ObjectType, + ObjectId: viewerUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -766,13 +778,15 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { // Assign custom-feature -> paid user paidUserHasFeature, err := Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: customFeature.ObjectType, - ObjectId: customFeature.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: paidUser.ObjectType, - ObjectId: paidUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: customFeature.ObjectType, + ObjectId: customFeature.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: paidUser.ObjectType, + ObjectId: paidUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -807,13 +821,15 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) paidUserHasFeature, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: customFeature.ObjectType, - ObjectId: customFeature.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: paidUser.ObjectType, - ObjectId: paidUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: customFeature.ObjectType, + ObjectId: customFeature.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: paidUser.ObjectType, + ObjectId: paidUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -854,13 +870,15 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) paidUserHasFeature, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: customFeature.ObjectType, - ObjectId: customFeature.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: paidUser.ObjectType, - ObjectId: paidUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: customFeature.ObjectType, + ObjectId: customFeature.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: paidUser.ObjectType, + ObjectId: paidUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -882,13 +900,15 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { // Assign feature-1 -> free tier -> free user freeUserHasFeature, err := Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: feature1.ObjectType, - ObjectId: feature1.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: freeUser.ObjectType, - ObjectId: freeUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: feature1.ObjectType, + ObjectId: feature1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: freeUser.ObjectType, + ObjectId: freeUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -947,13 +967,15 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) freeUserHasFeature, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: feature1.ObjectType, - ObjectId: feature1.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: freeUser.ObjectType, - ObjectId: freeUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: feature1.ObjectType, + ObjectId: feature1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: freeUser.ObjectType, + ObjectId: freeUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -1022,13 +1044,15 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) freeUserHasFeature, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: feature1.ObjectType, - ObjectId: feature1.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: freeUser.ObjectType, - ObjectId: freeUser.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: feature1.ObjectType, + ObjectId: feature1.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: freeUser.ObjectType, + ObjectId: freeUser.ObjectId, + }, }, }, WarrantToken: "latest", @@ -1140,13 +1164,15 @@ func TestWarrants(t *testing.T) { } userHasPermission, err := Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: newPermission.ObjectType, - ObjectId: newPermission.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: newPermission.ObjectType, + ObjectId: newPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }, }, }, WarrantToken: "latest", @@ -1229,13 +1255,15 @@ func TestWarrants(t *testing.T) { require.Equal(t, user1.ObjectId, warrants3.Data[0].Subject.ObjectId) userHasPermission, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: newPermission.ObjectType, - ObjectId: newPermission.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: newPermission.ObjectType, + ObjectId: newPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }, }, }, WarrantToken: "latest", @@ -1273,13 +1301,15 @@ func TestWarrants(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) userHasPermission, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: newPermission.ObjectType, - ObjectId: newPermission.ObjectId, - Relation: "member", - Subject: Subject{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + Checks: []WarrantCheck{ + { + ObjectType: newPermission.ObjectType, + ObjectId: newPermission.ObjectId, + Relation: "member", + Subject: Subject{ + ObjectType: user1.ObjectType, + ObjectId: user1.ObjectId, + }, }, }, WarrantToken: "latest", @@ -1345,8 +1375,8 @@ func TestBatchWarrants(t *testing.T) { t.Fatal(err) } - userHasPermissions, err := BatchCheck(context.Background(), CheckBatchOpts{ - Warrants: []WarrantCheck{ + userHasPermissions, err := CheckBatch(context.Background(), CheckBatchOpts{ + Checks: []WarrantCheck{ { ObjectType: permission1.ObjectType, ObjectId: permission1.ObjectId, @@ -1401,8 +1431,8 @@ func TestBatchWarrants(t *testing.T) { } require.NotEmpty(t, warrantResponse.WarrantToken) - userHasPermissions, err = BatchCheck(context.Background(), CheckBatchOpts{ - Warrants: []WarrantCheck{ + userHasPermissions, err = CheckBatch(context.Background(), CheckBatchOpts{ + Checks: []WarrantCheck{ { ObjectType: permission1.ObjectType, ObjectId: permission1.ObjectId, @@ -1458,8 +1488,8 @@ func TestBatchWarrants(t *testing.T) { } require.NotEmpty(t, warrantResponse.WarrantToken) - userHasPermissions, err = BatchCheck(context.Background(), CheckBatchOpts{ - Warrants: []WarrantCheck{ + userHasPermissions, err = CheckBatch(context.Background(), CheckBatchOpts{ + Checks: []WarrantCheck{ { ObjectType: permission1.ObjectType, ObjectId: permission1.ObjectId, @@ -1531,16 +1561,18 @@ func TestWarrantsWithPolicy(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) checkResult, err := Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: "permission", - ObjectId: "test-permission", - Relation: "member", - Subject: Subject{ - ObjectType: "user", - ObjectId: "user-1", - }, - Context: map[string]interface{}{ - "geo": "us", + Checks: []WarrantCheck{ + { + ObjectType: "permission", + ObjectId: "test-permission", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user-1", + }, + Context: map[string]interface{}{ + "geo": "us", + }, }, }, WarrantToken: "latest", @@ -1551,16 +1583,18 @@ func TestWarrantsWithPolicy(t *testing.T) { require.True(t, checkResult.Authorized()) checkResult, err = Check(context.Background(), CheckOpts{ - Warrant: WarrantCheck{ - ObjectType: "permission", - ObjectId: "test-permission", - Relation: "member", - Subject: Subject{ - ObjectType: "user", - ObjectId: "user-1", - }, - Context: map[string]interface{}{ - "geo": "eu", + Checks: []WarrantCheck{ + { + ObjectType: "permission", + ObjectId: "test-permission", + Relation: "member", + Subject: Subject{ + ObjectType: "user", + ObjectId: "user-1", + }, + Context: map[string]interface{}{ + "geo": "eu", + }, }, }, WarrantToken: "latest", diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go index 7f509db5..b03cb1e1 100644 --- a/pkg/fga/client_test.go +++ b/pkg/fga/client_test.go @@ -1118,7 +1118,7 @@ func TestCheckBatch(t *testing.T) { APIKey: "test", }, options: CheckBatchOpts{ - Warrants: []WarrantCheck{ + Checks: []WarrantCheck{ { ObjectType: "report", ObjectId: "ljc_1029", diff --git a/pkg/fga/fga.go b/pkg/fga/fga.go index 285b51ab..0ad3ddb2 100644 --- a/pkg/fga/fga.go +++ b/pkg/fga/fga.go @@ -94,7 +94,7 @@ func BatchWriteWarrants( return DefaultClient.BatchWriteWarrants(ctx, opts) } -// Check performs an access check on a Warrant. +// Check performs access checks on multiple Warrants. func Check( ctx context.Context, opts CheckOpts, @@ -102,16 +102,8 @@ func Check( return DefaultClient.Check(ctx, opts) } -// CheckMany performs access checks on multiple Warrants. -func CheckMany( - ctx context.Context, - opts CheckManyOpts, -) (CheckResponse, error) { - return DefaultClient.CheckMany(ctx, opts) -} - -// BatchCheck performs individual access checks on multiple Warrants in one request. -func BatchCheck( +// CheckBatch performs individual access checks on multiple Warrants in one request. +func CheckBatch( ctx context.Context, opts CheckBatchOpts, ) ([]CheckResponse, error) { diff --git a/pkg/fga/fga_test.go b/pkg/fga/fga_test.go index e14c50d0..b1ff62a3 100644 --- a/pkg/fga/fga_test.go +++ b/pkg/fga/fga_test.go @@ -342,8 +342,8 @@ func TestFGABatchWriteWarrants(t *testing.T) { require.Equal(t, expectedResponse, warrantResponse) } -func TestFGACheckMany(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(checkManyTestHandler)) +func TestFGACheck(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(checkTestHandler)) defer server.Close() DefaultClient = &Client{ @@ -352,7 +352,7 @@ func TestFGACheckMany(t *testing.T) { } SetAPIKey("test") - checkResponse, err := CheckMany(context.Background(), CheckManyOpts{ + checkResponse, err := Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { ObjectType: "report", @@ -370,8 +370,8 @@ func TestFGACheckMany(t *testing.T) { require.True(t, checkResponse.Authorized()) } -func TestFGABatchCheck(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(batchCheckTestHandler)) +func TestFGACheckBatch(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(checkBatchTestHandler)) defer server.Close() DefaultClient = &Client{ @@ -380,8 +380,8 @@ func TestFGABatchCheck(t *testing.T) { } SetAPIKey("test") - checkResponses, err := BatchCheck(context.Background(), CheckBatchOpts{ - Warrants: []WarrantCheck{ + checkResponses, err := CheckBatch(context.Background(), CheckBatchOpts{ + Checks: []WarrantCheck{ { ObjectType: "report", ObjectId: "ljc_1029", From 2f0d0046fcf66691697adcfe42f5ea97988ab000 Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Wed, 17 Jul 2024 10:27:13 -0700 Subject: [PATCH 12/17] Rename fga Object to Resource --- pkg/fga/client.go | 270 ++++---- pkg/fga/client_live_example.go | 1158 ++++++++++++++++---------------- pkg/fga/client_test.go | 432 ++++++------ pkg/fga/fga.go | 68 +- pkg/fga/fga_test.go | 202 +++--- 5 files changed, 1065 insertions(+), 1065 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index 524d7e8c..879c424f 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -60,31 +60,31 @@ func (c *Client) init() { } } -// Objects -type Object struct { - // The type of the object. - ObjectType string `json:"object_type"` +// Resources +type Resource struct { + // The type of the resource. + ResourceType string `json:"resource_type"` - // The customer defined string identifier for this object. - ObjectId string `json:"object_id"` + // The customer defined string identifier for this resource. + ResourceId string `json:"resource_id"` - // Map containing additional information about this object. + // Map containing additional information about this resource. Meta map[string]interface{} `json:"meta"` } -type GetObjectOpts struct { - // The type of the object. - ObjectType string +type GetResourceOpts struct { + // The type of the resource. + ResourceType string - // The customer defined string identifier for this object. - ObjectId string + // The customer defined string identifier for this resource. + ResourceId string } -type ListObjectsOpts struct { - // The type of the object. - ObjectType string `url:"object_type,omitempty"` +type ListResourcesOpts struct { + // The type of the resource. + ResourceType string `url:"resource_type,omitempty"` - // Searchable text for an Object. Can be empty. + // Searchable text for an Resource. Can be empty. Search string `url:"search,omitempty"` // Maximum number of records to return. @@ -93,112 +93,112 @@ type ListObjectsOpts struct { // The order in which to paginate records. Order Order `url:"order,omitempty"` - // Pagination cursor to receive records before a provided Object ID. + // Pagination cursor to receive records before a provided Resource ID. Before string `url:"before,omitempty"` - // Pagination cursor to receive records after a provided Object ID. + // Pagination cursor to receive records after a provided Resource ID. After string `url:"after,omitempty"` } -// ListObjectsResponse describes the response structure when requesting Objects -type ListObjectsResponse struct { - // List of provisioned Objects. - Data []Object `json:"data"` +// ListResourcesResponse describes the response structure when requesting Resources +type ListResourcesResponse struct { + // List of provisioned Resources. + Data []Resource `json:"data"` // Cursor pagination options. ListMetadata common.ListMetadata `json:"list_metadata"` } -type CreateObjectOpts struct { - // The type of the object. - ObjectType string `json:"object_type"` +type CreateResourceOpts struct { + // The type of the resource. + ResourceType string `json:"resource_type"` - // The customer defined string identifier for this object. - ObjectId string `json:"object_id,omitempty"` + // The customer defined string identifier for this resource. + ResourceId string `json:"resource_id,omitempty"` - // Map containing additional information about this object. + // Map containing additional information about this resource. Meta map[string]interface{} `json:"meta,omitempty"` } -type UpdateObjectOpts struct { - // The type of the object. - ObjectType string `json:"object_type"` +type UpdateResourceOpts struct { + // The type of the resource. + ResourceType string `json:"resource_type"` - // The customer defined string identifier for this object. - ObjectId string `json:"object_id"` + // The customer defined string identifier for this resource. + ResourceId string `json:"resource_id"` - // Map containing additional information about this object. + // Map containing additional information about this resource. Meta map[string]interface{} `json:"meta,omitempty"` } -// DeleteObjectOpts contains the options to delete an object. -type DeleteObjectOpts struct { - // The type of the object. - ObjectType string +// DeleteResourceOpts contains the options to delete an resource. +type DeleteResourceOpts struct { + // The type of the resource. + ResourceType string - // The customer defined string identifier for this object. - ObjectId string + // The customer defined string identifier for this resource. + ResourceId string } -// Object types -type ObjectType struct { - // Unique string ID of the object type. +// Resource types +type ResourceType struct { + // Unique string ID of the resource type. Type string `json:"type"` - // Set of relationships that subjects can have on objects of this type. + // Set of relationships that subjects can have on resources of this type. Relations map[string]interface{} `json:"relations"` } -type ListObjectTypesOpts struct { +type ListResourceTypesOpts struct { // Maximum number of records to return. Limit int `url:"limit,omitempty"` // The order in which to paginate records. Order Order `url:"order,omitempty"` - // Pagination cursor to receive records before a provided ObjectType ID. + // Pagination cursor to receive records before a provided ResourceType ID. Before string `url:"before,omitempty"` - // Pagination cursor to receive records after a provided ObjectType ID. + // Pagination cursor to receive records after a provided ResourceType ID. After string `url:"after,omitempty"` } -type ListObjectTypesResponse struct { - // List of Object Types. - Data []ObjectType `json:"data"` +type ListResourceTypesResponse struct { + // List of Resource Types. + Data []ResourceType `json:"data"` // Cursor pagination options. ListMetadata common.ListMetadata `json:"list_metadata"` } -type UpdateObjectTypeOpts struct { - // Unique string ID of the object type. +type UpdateResourceTypeOpts struct { + // Unique string ID of the resource type. Type string `json:"type"` - // Set of relationships that subjects can have on objects of this type. + // Set of relationships that subjects can have on resources of this type. Relations map[string]interface{} `json:"relations"` } // Warrants type Subject struct { // The type of the subject. - ObjectType string `json:"object_type"` + ResourceType string `json:"resource_type"` // The customer defined string identifier for this subject. - ObjectId string `json:"object_id"` + ResourceId string `json:"resource_id"` // The relation of the subject. Relation string `json:"relation,omitempty"` } type Warrant struct { - // Type of object to assign a relation to. Must be an existing type. - ObjectType string `json:"object_type"` + // Type of resource to assign a relation to. Must be an existing type. + ResourceType string `json:"resource_type"` - // Id of the object to assign a relation to. - ObjectId string `json:"object_id"` + // Id of the resource to assign a relation to. + ResourceId string `json:"resource_id"` - // Relation to assign to the object. + // Relation to assign to the resource. Relation string `json:"relation"` // Subject of the warrant @@ -209,19 +209,19 @@ type Warrant struct { } type ListWarrantsOpts struct { - // Only return warrants whose objectType matches this value. - ObjectType string `url:"object_type,omitempty"` + // Only return warrants whose resourceType matches this value. + ResourceType string `url:"resource_type,omitempty"` - // Only return warrants whose objectId matches this value. - ObjectId string `url:"object_id,omitempty"` + // Only return warrants whose resourceId matches this value. + ResourceId string `url:"resource_id,omitempty"` // Only return warrants whose relation matches this value. Relation string `url:"relation,omitempty"` - // Only return warrants with a subject whose objectType matches this value. + // Only return warrants with a subject whose resourceType matches this value. SubjectType string `url:"subject_type,omitempty"` - // Only return warrants with a subject whose objectId matches this value. + // Only return warrants with a subject whose resourceId matches this value. SubjectId string `url:"subject_id,omitempty"` // Only return warrants with a subject whose relation matches this value. @@ -250,13 +250,13 @@ type WriteWarrantOpts struct { // Operation to perform for the given warrant Op string `json:"op,omitempty"` - // Type of object to assign a relation to. Must be an existing type. - ObjectType string `json:"object_type"` + // Type of resource to assign a relation to. Must be an existing type. + ResourceType string `json:"resource_type"` - // Id of the object to assign a relation to. - ObjectId string `json:"object_id"` + // Id of the resource to assign a relation to. + ResourceId string `json:"resource_id"` - // Relation to assign to the object. + // Relation to assign to the resource. Relation string `json:"relation"` // Subject of the warrant @@ -274,13 +274,13 @@ type WriteWarrantResponse struct { type Context map[string]interface{} type WarrantCheck struct { - // The type of the object. - ObjectType string `json:"object_type"` + // The type of the resource. + ResourceType string `json:"resource_type"` - // Id of the specific object. - ObjectId string `json:"object_id"` + // Id of the specific resource. + ResourceId string `json:"resource_id"` - // Relation to check between the object and subject. + // Relation to check between the resource and subject. Relation string `json:"relation"` // The subject that must have the specified relation. @@ -364,13 +364,13 @@ type QueryOpts struct { } type QueryResult struct { - // The type of the object. - ObjectType string `json:"object_type"` + // The type of the resource. + ResourceType string `json:"resource_type"` - // Id of the specific object. - ObjectId string `json:"object_id"` + // Id of the specific resource. + ResourceId string `json:"resource_id"` - // Relation between the object and subject. + // Relation between the resource and subject. Relation string `json:"relation"` // Warrant matching the provided query @@ -379,7 +379,7 @@ type QueryResult struct { // Specifies whether the warrant is implicitly defined. IsImplicit bool `json:"is_implicit"` - // Metadata of the object. + // Metadata of the resource. Meta map[string]interface{} `json:"meta,omitempty"` } @@ -391,14 +391,14 @@ type QueryResponse struct { ListMetadata common.ListMetadata `json:"list_metadata"` } -// GetObject gets an Object. -func (c *Client) GetObject(ctx context.Context, opts GetObjectOpts) (Object, error) { +// GetResource gets an Resource. +func (c *Client) GetResource(ctx context.Context, opts GetResourceOpts) (Resource, error) { c.once.Do(c.init) - endpoint := fmt.Sprintf("%s/fga/v1/objects/%s/%s", c.Endpoint, opts.ObjectType, opts.ObjectId) + endpoint := fmt.Sprintf("%s/fga/v1/resources/%s/%s", c.Endpoint, opts.ResourceType, opts.ResourceId) req, err := http.NewRequest(http.MethodGet, endpoint, nil) if err != nil { - return Object{}, err + return Resource{}, err } req = req.WithContext(ctx) @@ -408,28 +408,28 @@ func (c *Client) GetObject(ctx context.Context, opts GetObjectOpts) (Object, err res, err := c.HTTPClient.Do(req) if err != nil { - return Object{}, err + return Resource{}, err } defer res.Body.Close() if err = workos_errors.TryGetHTTPError(res); err != nil { - return Object{}, err + return Resource{}, err } - var body Object + var body Resource dec := json.NewDecoder(res.Body) err = dec.Decode(&body) return body, err } -// ListObjects gets a list of FGA objects. -func (c *Client) ListObjects(ctx context.Context, opts ListObjectsOpts) (ListObjectsResponse, error) { +// ListResources gets a list of FGA resources. +func (c *Client) ListResources(ctx context.Context, opts ListResourcesOpts) (ListResourcesResponse, error) { c.once.Do(c.init) - endpoint := fmt.Sprintf("%s/fga/v1/objects", c.Endpoint) + endpoint := fmt.Sprintf("%s/fga/v1/resources", c.Endpoint) req, err := http.NewRequest(http.MethodGet, endpoint, nil) if err != nil { - return ListObjectsResponse{}, err + return ListResourcesResponse{}, err } req = req.WithContext(ctx) @@ -447,40 +447,40 @@ func (c *Client) ListObjects(ctx context.Context, opts ListObjectsOpts) (ListObj q, err := query.Values(opts) if err != nil { - return ListObjectsResponse{}, err + return ListResourcesResponse{}, err } req.URL.RawQuery = q.Encode() res, err := c.HTTPClient.Do(req) if err != nil { - return ListObjectsResponse{}, err + return ListResourcesResponse{}, err } defer res.Body.Close() if err = workos_errors.TryGetHTTPError(res); err != nil { - return ListObjectsResponse{}, err + return ListResourcesResponse{}, err } - var body ListObjectsResponse + var body ListResourcesResponse dec := json.NewDecoder(res.Body) err = dec.Decode(&body) return body, err } -// CreateObject creates a new object -func (c *Client) CreateObject(ctx context.Context, opts CreateObjectOpts) (Object, error) { +// CreateResource creates a new resource +func (c *Client) CreateResource(ctx context.Context, opts CreateResourceOpts) (Resource, error) { c.once.Do(c.init) data, err := c.JSONEncode(opts) if err != nil { - return Object{}, err + return Resource{}, err } - endpoint := fmt.Sprintf("%s/fga/v1/objects", c.Endpoint) + endpoint := fmt.Sprintf("%s/fga/v1/resources", c.Endpoint) req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(data)) if err != nil { - return Object{}, err + return Resource{}, err } req = req.WithContext(ctx) @@ -490,40 +490,40 @@ func (c *Client) CreateObject(ctx context.Context, opts CreateObjectOpts) (Objec res, err := c.HTTPClient.Do(req) if err != nil { - return Object{}, err + return Resource{}, err } defer res.Body.Close() if err = workos_errors.TryGetHTTPError(res); err != nil { - return Object{}, err + return Resource{}, err } - var body Object + var body Resource dec := json.NewDecoder(res.Body) err = dec.Decode(&body) return body, err } -// UpdateObject updates an existing Object -func (c *Client) UpdateObject(ctx context.Context, opts UpdateObjectOpts) (Object, error) { +// UpdateResource updates an existing Resource +func (c *Client) UpdateResource(ctx context.Context, opts UpdateResourceOpts) (Resource, error) { c.once.Do(c.init) - // UpdateObjectChangeOpts contains the options to update an Object minus the ObjectType and ObjectId - type UpdateObjectChangeOpts struct { + // UpdateResourceChangeOpts contains the options to update an Resource minus the ResourceType and ResourceId + type UpdateResourceChangeOpts struct { Meta map[string]interface{} `json:"meta"` } - update_opts := UpdateObjectChangeOpts{Meta: opts.Meta} + update_opts := UpdateResourceChangeOpts{Meta: opts.Meta} data, err := c.JSONEncode(update_opts) if err != nil { - return Object{}, err + return Resource{}, err } - endpoint := fmt.Sprintf("%s/fga/v1/objects/%s/%s", c.Endpoint, opts.ObjectType, opts.ObjectId) + endpoint := fmt.Sprintf("%s/fga/v1/resources/%s/%s", c.Endpoint, opts.ResourceType, opts.ResourceId) req, err := http.NewRequest(http.MethodPut, endpoint, bytes.NewBuffer(data)) if err != nil { - return Object{}, err + return Resource{}, err } req = req.WithContext(ctx) @@ -533,26 +533,26 @@ func (c *Client) UpdateObject(ctx context.Context, opts UpdateObjectOpts) (Objec res, err := c.HTTPClient.Do(req) if err != nil { - return Object{}, err + return Resource{}, err } defer res.Body.Close() if err = workos_errors.TryGetHTTPError(res); err != nil { - return Object{}, err + return Resource{}, err } - var body Object + var body Resource dec := json.NewDecoder(res.Body) err = dec.Decode(&body) return body, err } -// DeleteObject deletes an Object -func (c *Client) DeleteObject(ctx context.Context, opts DeleteObjectOpts) error { +// DeleteResource deletes an Resource +func (c *Client) DeleteResource(ctx context.Context, opts DeleteResourceOpts) error { c.once.Do(c.init) - endpoint := fmt.Sprintf("%s/fga/v1/objects/%s/%s", c.Endpoint, opts.ObjectType, opts.ObjectId) + endpoint := fmt.Sprintf("%s/fga/v1/resources/%s/%s", c.Endpoint, opts.ResourceType, opts.ResourceId) req, err := http.NewRequest(http.MethodDelete, endpoint, nil) if err != nil { return err @@ -572,14 +572,14 @@ func (c *Client) DeleteObject(ctx context.Context, opts DeleteObjectOpts) error return workos_errors.TryGetHTTPError(res) } -// ListObjectTypes gets a list of FGA object types. -func (c *Client) ListObjectTypes(ctx context.Context, opts ListObjectTypesOpts) (ListObjectTypesResponse, error) { +// ListResourceTypes gets a list of FGA resource types. +func (c *Client) ListResourceTypes(ctx context.Context, opts ListResourceTypesOpts) (ListResourceTypesResponse, error) { c.once.Do(c.init) - endpoint := fmt.Sprintf("%s/fga/v1/object-types", c.Endpoint) + endpoint := fmt.Sprintf("%s/fga/v1/resource-types", c.Endpoint) req, err := http.NewRequest(http.MethodGet, endpoint, nil) if err != nil { - return ListObjectTypesResponse{}, err + return ListResourceTypesResponse{}, err } req = req.WithContext(ctx) @@ -597,40 +597,40 @@ func (c *Client) ListObjectTypes(ctx context.Context, opts ListObjectTypesOpts) q, err := query.Values(opts) if err != nil { - return ListObjectTypesResponse{}, err + return ListResourceTypesResponse{}, err } req.URL.RawQuery = q.Encode() res, err := c.HTTPClient.Do(req) if err != nil { - return ListObjectTypesResponse{}, err + return ListResourceTypesResponse{}, err } defer res.Body.Close() if err = workos_errors.TryGetHTTPError(res); err != nil { - return ListObjectTypesResponse{}, err + return ListResourceTypesResponse{}, err } - var body ListObjectTypesResponse + var body ListResourceTypesResponse dec := json.NewDecoder(res.Body) err = dec.Decode(&body) return body, err } -// BatchUpdateObjectTypes sets the environment's set of object types to match the object types passed. -func (c *Client) BatchUpdateObjectTypes(ctx context.Context, opts []UpdateObjectTypeOpts) ([]ObjectType, error) { +// BatchUpdateResourceTypes sets the environment's set of resource types to match the resource types passed. +func (c *Client) BatchUpdateResourceTypes(ctx context.Context, opts []UpdateResourceTypeOpts) ([]ResourceType, error) { c.once.Do(c.init) data, err := c.JSONEncode(opts) if err != nil { - return []ObjectType{}, err + return []ResourceType{}, err } - endpoint := fmt.Sprintf("%s/fga/v1/object-types", c.Endpoint) + endpoint := fmt.Sprintf("%s/fga/v1/resource-types", c.Endpoint) req, err := http.NewRequest(http.MethodPut, endpoint, bytes.NewBuffer(data)) if err != nil { - return []ObjectType{}, err + return []ResourceType{}, err } req = req.WithContext(ctx) @@ -640,15 +640,15 @@ func (c *Client) BatchUpdateObjectTypes(ctx context.Context, opts []UpdateObject res, err := c.HTTPClient.Do(req) if err != nil { - return []ObjectType{}, err + return []ResourceType{}, err } defer res.Body.Close() if err = workos_errors.TryGetHTTPError(res); err != nil { - return []ObjectType{}, err + return []ResourceType{}, err } - var body []ObjectType + var body []ResourceType dec := json.NewDecoder(res.Body) err = dec.Decode(&body) return body, err diff --git a/pkg/fga/client_live_example.go b/pkg/fga/client_live_example.go index 53de7531..a99b43de 100644 --- a/pkg/fga/client_live_example.go +++ b/pkg/fga/client_live_example.go @@ -12,140 +12,140 @@ func setup() { SetAPIKey("") } -func TestCrudObjects(t *testing.T) { +func TestCrudResources(t *testing.T) { setup() - object1, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "document", + resource1, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "document", }) if err != nil { t.Fatal(err) } - require.Equal(t, "document", object1.ObjectType) - require.NotEmpty(t, object1.ObjectId) - require.Empty(t, object1.Meta) + require.Equal(t, "document", resource1.ResourceType) + require.NotEmpty(t, resource1.ResourceId) + require.Empty(t, resource1.Meta) - object2, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "folder", - ObjectId: "planning", + resource2, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "folder", + ResourceId: "planning", }) if err != nil { t.Fatal(err) } - refetchedObject, err := GetObject(context.Background(), GetObjectOpts{ - ObjectType: object2.ObjectType, - ObjectId: object2.ObjectId, + refetchedResource, err := GetResource(context.Background(), GetResourceOpts{ + ResourceType: resource2.ResourceType, + ResourceId: resource2.ResourceId, }) if err != nil { t.Fatal(err) } - require.Equal(t, object2.ObjectType, refetchedObject.ObjectType) - require.Equal(t, object2.ObjectId, refetchedObject.ObjectId) - require.EqualValues(t, object2.Meta, refetchedObject.Meta) + require.Equal(t, resource2.ResourceType, refetchedResource.ResourceType) + require.Equal(t, resource2.ResourceId, refetchedResource.ResourceId) + require.EqualValues(t, resource2.Meta, refetchedResource.Meta) - object2, err = UpdateObject(context.Background(), UpdateObjectOpts{ - ObjectType: object2.ObjectType, - ObjectId: object2.ObjectId, + resource2, err = UpdateResource(context.Background(), UpdateResourceOpts{ + ResourceType: resource2.ResourceType, + ResourceId: resource2.ResourceId, Meta: map[string]interface{}{ - "description": "Folder object", + "description": "Folder resource", }, }) if err != nil { t.Fatal(err) } - refetchedObject, err = GetObject(context.Background(), GetObjectOpts{ - ObjectType: object2.ObjectType, - ObjectId: object2.ObjectId, + refetchedResource, err = GetResource(context.Background(), GetResourceOpts{ + ResourceType: resource2.ResourceType, + ResourceId: resource2.ResourceId, }) if err != nil { t.Fatal(err) } - require.Equal(t, object2.ObjectType, refetchedObject.ObjectType) - require.Equal(t, object2.ObjectId, refetchedObject.ObjectId) - require.EqualValues(t, object2.Meta, refetchedObject.Meta) + require.Equal(t, resource2.ResourceType, refetchedResource.ResourceType) + require.Equal(t, resource2.ResourceId, refetchedResource.ResourceId) + require.EqualValues(t, resource2.Meta, refetchedResource.Meta) - objectsList, err := ListObjects(context.Background(), ListObjectsOpts{ + resourcesList, err := ListResources(context.Background(), ListResourcesOpts{ Limit: 10, }) if err != nil { t.Fatal(err) } - require.Len(t, objectsList.Data, 2) - require.Equal(t, object2.ObjectType, objectsList.Data[0].ObjectType) - require.Equal(t, object2.ObjectId, objectsList.Data[0].ObjectId) - require.Equal(t, object1.ObjectType, objectsList.Data[1].ObjectType) - require.Equal(t, object1.ObjectId, objectsList.Data[1].ObjectId) + require.Len(t, resourcesList.Data, 2) + require.Equal(t, resource2.ResourceType, resourcesList.Data[0].ResourceType) + require.Equal(t, resource2.ResourceId, resourcesList.Data[0].ResourceId) + require.Equal(t, resource1.ResourceType, resourcesList.Data[1].ResourceType) + require.Equal(t, resource1.ResourceId, resourcesList.Data[1].ResourceId) // Sort in ascending order - objectsList, err = ListObjects(context.Background(), ListObjectsOpts{ + resourcesList, err = ListResources(context.Background(), ListResourcesOpts{ Limit: 10, Order: Asc, }) if err != nil { t.Fatal(err) } - require.Len(t, objectsList.Data, 2) - require.Equal(t, object1.ObjectType, objectsList.Data[0].ObjectType) - require.Equal(t, object1.ObjectId, objectsList.Data[0].ObjectId) - require.Equal(t, object2.ObjectType, objectsList.Data[1].ObjectType) - require.Equal(t, object2.ObjectId, objectsList.Data[1].ObjectId) + require.Len(t, resourcesList.Data, 2) + require.Equal(t, resource1.ResourceType, resourcesList.Data[0].ResourceType) + require.Equal(t, resource1.ResourceId, resourcesList.Data[0].ResourceId) + require.Equal(t, resource2.ResourceType, resourcesList.Data[1].ResourceType) + require.Equal(t, resource2.ResourceId, resourcesList.Data[1].ResourceId) - objectsList, err = ListObjects(context.Background(), ListObjectsOpts{ + resourcesList, err = ListResources(context.Background(), ListResourcesOpts{ Limit: 10, Search: "planning", }) if err != nil { t.Fatal(err) } - require.Len(t, objectsList.Data, 1) - require.Equal(t, object2.ObjectType, objectsList.Data[0].ObjectType) - require.Equal(t, object2.ObjectId, objectsList.Data[0].ObjectId) + require.Len(t, resourcesList.Data, 1) + require.Equal(t, resource2.ResourceType, resourcesList.Data[0].ResourceType) + require.Equal(t, resource2.ResourceId, resourcesList.Data[0].ResourceId) - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: object1.ObjectType, - ObjectId: object1.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: resource1.ResourceType, + ResourceId: resource1.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: object2.ObjectType, - ObjectId: object2.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: resource2.ResourceType, + ResourceId: resource2.ResourceId, }) if err != nil { t.Fatal(err) } - objectsList, err = ListObjects(context.Background(), ListObjectsOpts{ + resourcesList, err = ListResources(context.Background(), ListResourcesOpts{ Limit: 10, Search: "planning", }) if err != nil { t.Fatal(err) } - require.Len(t, objectsList.Data, 0) + require.Len(t, resourcesList.Data, 0) } func TestMultiTenancy(t *testing.T) { setup() // Create users - user1, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", + user1, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", }) if err != nil { t.Fatal(err) } - user2, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", + user2, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", }) if err != nil { t.Fatal(err) } // Create tenants - tenant1, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "tenant", - ObjectId: "tenant-1", + tenant1, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "tenant", + ResourceId: "tenant-1", Meta: map[string]interface{}{ "name": "Tenant 1", }, @@ -153,9 +153,9 @@ func TestMultiTenancy(t *testing.T) { if err != nil { t.Fatal(err) } - tenant2, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "tenant", - ObjectId: "tenant-2", + tenant2, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "tenant", + ResourceId: "tenant-2", Meta: map[string]interface{}{ "name": "Tenant 2", }, @@ -165,7 +165,7 @@ func TestMultiTenancy(t *testing.T) { } user1TenantsList, err := Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select tenant where user:%s is member", user1.ObjectId), + Query: fmt.Sprintf("select tenant where user:%s is member", user1.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -174,7 +174,7 @@ func TestMultiTenancy(t *testing.T) { } require.Len(t, user1TenantsList.Data, 0) tenant1UsersList, err := Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select member of type user for tenant:%s", tenant1.ObjectId), + Query: fmt.Sprintf("select member of type user for tenant:%s", tenant1.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -185,12 +185,12 @@ func TestMultiTenancy(t *testing.T) { // Assign user1 -> tenant1 warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ - ObjectType: tenant1.ObjectType, - ObjectId: tenant1.ObjectId, - Relation: "member", + ResourceType: tenant1.ResourceType, + ResourceId: tenant1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + ResourceType: user1.ResourceType, + ResourceId: user1.ResourceId, }, }) if err != nil { @@ -199,7 +199,7 @@ func TestMultiTenancy(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) user1TenantsList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select tenant where user:%s is member", user1.ObjectId), + Query: fmt.Sprintf("select tenant where user:%s is member", user1.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -207,14 +207,14 @@ func TestMultiTenancy(t *testing.T) { t.Fatal(err) } require.Len(t, user1TenantsList.Data, 1) - require.Equal(t, "tenant", user1TenantsList.Data[0].ObjectType) - require.Equal(t, "tenant-1", user1TenantsList.Data[0].ObjectId) + require.Equal(t, "tenant", user1TenantsList.Data[0].ResourceType) + require.Equal(t, "tenant-1", user1TenantsList.Data[0].ResourceId) require.EqualValues(t, map[string]interface{}{ "name": "Tenant 1", }, user1TenantsList.Data[0].Meta) tenant1UsersList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select member of type user for tenant:%s", tenant1.ObjectId), + Query: fmt.Sprintf("select member of type user for tenant:%s", tenant1.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -222,19 +222,19 @@ func TestMultiTenancy(t *testing.T) { t.Fatal(err) } require.Len(t, tenant1UsersList.Data, 1) - require.Equal(t, "user", tenant1UsersList.Data[0].ObjectType) - require.Equal(t, user1.ObjectId, tenant1UsersList.Data[0].ObjectId) + require.Equal(t, "user", tenant1UsersList.Data[0].ResourceType) + require.Equal(t, user1.ResourceId, tenant1UsersList.Data[0].ResourceId) require.Empty(t, tenant1UsersList.Data[0].Meta) // Remove user1 -> tenant1 warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - Op: "delete", - ObjectType: tenant1.ObjectType, - ObjectId: tenant1.ObjectId, - Relation: "member", + Op: "delete", + ResourceType: tenant1.ResourceType, + ResourceId: tenant1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + ResourceType: user1.ResourceType, + ResourceId: user1.ResourceId, }, }) if err != nil { @@ -243,7 +243,7 @@ func TestMultiTenancy(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) user1TenantsList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select tenant where user:%s is member", user1.ObjectId), + Query: fmt.Sprintf("select tenant where user:%s is member", user1.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -252,7 +252,7 @@ func TestMultiTenancy(t *testing.T) { } require.Len(t, user1TenantsList.Data, 0) tenant1UsersList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select member of type user for tenant:%s", tenant1.ObjectId), + Query: fmt.Sprintf("select member of type user for tenant:%s", tenant1.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -262,30 +262,30 @@ func TestMultiTenancy(t *testing.T) { require.Len(t, tenant1UsersList.Data, 0) // Clean up - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: user1.ResourceType, + ResourceId: user1.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: user2.ObjectType, - ObjectId: user2.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: user2.ResourceType, + ResourceId: user2.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: tenant1.ObjectType, - ObjectId: tenant1.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: tenant1.ResourceType, + ResourceId: tenant1.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: tenant2.ObjectType, - ObjectId: tenant2.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: tenant2.ResourceType, + ResourceId: tenant2.ResourceId, }) if err != nil { t.Fatal(err) @@ -296,23 +296,23 @@ func TestRBAC(t *testing.T) { setup() // Create users - adminUser, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", + adminUser, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", }) if err != nil { t.Fatal(err) } - viewerUser, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", + viewerUser, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", }) if err != nil { t.Fatal(err) } // Create roles - adminRole, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "role", - ObjectId: "administrator", + adminRole, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "role", + ResourceId: "administrator", Meta: map[string]interface{}{ "name": "Administrator", "description": "The admin role", @@ -321,9 +321,9 @@ func TestRBAC(t *testing.T) { if err != nil { t.Fatal(err) } - viewerRole, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "role", - ObjectId: "viewer", + viewerRole, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "role", + ResourceId: "viewer", Meta: map[string]interface{}{ "name": "Viewer", "description": "The viewer role", @@ -334,9 +334,9 @@ func TestRBAC(t *testing.T) { } // Create permissions - createPermission, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "permission", - ObjectId: "create-report", + createPermission, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "permission", + ResourceId: "create-report", Meta: map[string]interface{}{ "name": "Create Report", "description": "Permission to create reports", @@ -345,9 +345,9 @@ func TestRBAC(t *testing.T) { if err != nil { t.Fatal(err) } - viewPermission, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "permission", - ObjectId: "view-report", + viewPermission, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "permission", + ResourceId: "view-report", Meta: map[string]interface{}{ "name": "View Report", "description": "Permission to view reports", @@ -358,7 +358,7 @@ func TestRBAC(t *testing.T) { } adminUserRolesList, err := Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select role where user:%s is member", adminUser.ObjectId), + Query: fmt.Sprintf("select role where user:%s is member", adminUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -368,7 +368,7 @@ func TestRBAC(t *testing.T) { require.Len(t, adminUserRolesList.Data, 0) adminRolePermissionsList, err := Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select permission where role:%s is member", adminRole.ObjectId), + Query: fmt.Sprintf("select permission where role:%s is member", adminRole.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -380,12 +380,12 @@ func TestRBAC(t *testing.T) { adminUserHasPermission, err := Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: createPermission.ObjectType, - ObjectId: createPermission.ObjectId, - Relation: "member", + ResourceType: createPermission.ResourceType, + ResourceId: createPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: adminUser.ObjectType, - ObjectId: adminUser.ObjectId, + ResourceType: adminUser.ResourceType, + ResourceId: adminUser.ResourceId, }, }, }, @@ -398,12 +398,12 @@ func TestRBAC(t *testing.T) { // Assign create-report permission -> admin role -> admin user warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ - ObjectType: createPermission.ObjectType, - ObjectId: createPermission.ObjectId, - Relation: "member", + ResourceType: createPermission.ResourceType, + ResourceId: createPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: adminRole.ObjectType, - ObjectId: adminRole.ObjectId, + ResourceType: adminRole.ResourceType, + ResourceId: adminRole.ResourceId, }, }) if err != nil { @@ -412,12 +412,12 @@ func TestRBAC(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - ObjectType: adminRole.ObjectType, - ObjectId: adminRole.ObjectId, - Relation: "member", + ResourceType: adminRole.ResourceType, + ResourceId: adminRole.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: adminUser.ObjectType, - ObjectId: adminUser.ObjectId, + ResourceType: adminUser.ResourceType, + ResourceId: adminUser.ResourceId, }, }) if err != nil { @@ -428,12 +428,12 @@ func TestRBAC(t *testing.T) { adminUserHasPermission, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: createPermission.ObjectType, - ObjectId: createPermission.ObjectId, - Relation: "member", + ResourceType: createPermission.ResourceType, + ResourceId: createPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: adminUser.ObjectType, - ObjectId: adminUser.ObjectId, + ResourceType: adminUser.ResourceType, + ResourceId: adminUser.ResourceId, }, }, }, @@ -445,7 +445,7 @@ func TestRBAC(t *testing.T) { require.True(t, adminUserHasPermission.Authorized()) adminUserRolesList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select role where user:%s is member", adminUser.ObjectId), + Query: fmt.Sprintf("select role where user:%s is member", adminUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -453,15 +453,15 @@ func TestRBAC(t *testing.T) { t.Fatal(err) } require.Len(t, adminUserRolesList.Data, 1) - require.Equal(t, "role", adminUserRolesList.Data[0].ObjectType) - require.Equal(t, adminRole.ObjectId, adminUserRolesList.Data[0].ObjectId) + require.Equal(t, "role", adminUserRolesList.Data[0].ResourceType) + require.Equal(t, adminRole.ResourceId, adminUserRolesList.Data[0].ResourceId) require.Equal(t, map[string]interface{}{ "name": "Administrator", "description": "The admin role", }, adminUserRolesList.Data[0].Meta) adminRolePermissionsList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select permission where role:%s is member", adminRole.ObjectId), + Query: fmt.Sprintf("select permission where role:%s is member", adminRole.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -469,8 +469,8 @@ func TestRBAC(t *testing.T) { t.Fatal(err) } require.Len(t, adminRolePermissionsList.Data, 1) - require.Equal(t, "permission", adminRolePermissionsList.Data[0].ObjectType) - require.Equal(t, createPermission.ObjectId, adminRolePermissionsList.Data[0].ObjectId) + require.Equal(t, "permission", adminRolePermissionsList.Data[0].ResourceType) + require.Equal(t, createPermission.ResourceId, adminRolePermissionsList.Data[0].ResourceId) require.Equal(t, map[string]interface{}{ "name": "Create Report", "description": "Permission to create reports", @@ -478,13 +478,13 @@ func TestRBAC(t *testing.T) { // Remove create-report permission -> admin role -> admin user warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - Op: "delete", - ObjectType: createPermission.ObjectType, - ObjectId: createPermission.ObjectId, - Relation: "member", + Op: "delete", + ResourceType: createPermission.ResourceType, + ResourceId: createPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: adminRole.ObjectType, - ObjectId: adminRole.ObjectId, + ResourceType: adminRole.ResourceType, + ResourceId: adminRole.ResourceId, }, }) if err != nil { @@ -493,13 +493,13 @@ func TestRBAC(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - Op: "delete", - ObjectType: adminRole.ObjectType, - ObjectId: adminRole.ObjectId, - Relation: "member", + Op: "delete", + ResourceType: adminRole.ResourceType, + ResourceId: adminRole.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: adminUser.ObjectType, - ObjectId: adminUser.ObjectId, + ResourceType: adminUser.ResourceType, + ResourceId: adminUser.ResourceId, }, }) if err != nil { @@ -510,12 +510,12 @@ func TestRBAC(t *testing.T) { adminUserHasPermission, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: createPermission.ObjectType, - ObjectId: createPermission.ObjectId, - Relation: "member", + ResourceType: createPermission.ResourceType, + ResourceId: createPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: adminUser.ObjectType, - ObjectId: adminUser.ObjectId, + ResourceType: adminUser.ResourceType, + ResourceId: adminUser.ResourceId, }, }, }, @@ -527,7 +527,7 @@ func TestRBAC(t *testing.T) { require.False(t, adminUserHasPermission.Authorized()) adminUserRolesList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select role where user:%s is member", adminUser.ObjectId), + Query: fmt.Sprintf("select role where user:%s is member", adminUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -537,7 +537,7 @@ func TestRBAC(t *testing.T) { require.Len(t, adminUserRolesList.Data, 0) adminRolePermissionsList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select permission where role:%s is member", adminRole.ObjectId), + Query: fmt.Sprintf("select permission where role:%s is member", adminRole.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -550,12 +550,12 @@ func TestRBAC(t *testing.T) { viewerUserHasPermission, err := Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: viewPermission.ObjectType, - ObjectId: viewPermission.ObjectId, - Relation: "member", + ResourceType: viewPermission.ResourceType, + ResourceId: viewPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: viewerUser.ObjectType, - ObjectId: viewerUser.ObjectId, + ResourceType: viewerUser.ResourceType, + ResourceId: viewerUser.ResourceId, }, }, }, @@ -567,7 +567,7 @@ func TestRBAC(t *testing.T) { require.False(t, viewerUserHasPermission.Authorized()) viewerUserPermissionsList, err := Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ObjectId), + Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -577,12 +577,12 @@ func TestRBAC(t *testing.T) { require.Empty(t, viewerUserPermissionsList.Data) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - ObjectType: viewPermission.ObjectType, - ObjectId: viewPermission.ObjectId, - Relation: "member", + ResourceType: viewPermission.ResourceType, + ResourceId: viewPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: viewerUser.ObjectType, - ObjectId: viewerUser.ObjectId, + ResourceType: viewerUser.ResourceType, + ResourceId: viewerUser.ResourceId, }, }) if err != nil { @@ -593,12 +593,12 @@ func TestRBAC(t *testing.T) { viewerUserHasPermission, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: viewPermission.ObjectType, - ObjectId: viewPermission.ObjectId, - Relation: "member", + ResourceType: viewPermission.ResourceType, + ResourceId: viewPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: viewerUser.ObjectType, - ObjectId: viewerUser.ObjectId, + ResourceType: viewerUser.ResourceType, + ResourceId: viewerUser.ResourceId, }, }, }, @@ -610,7 +610,7 @@ func TestRBAC(t *testing.T) { require.True(t, viewerUserHasPermission.Authorized()) viewerUserPermissionsList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ObjectId), + Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -618,21 +618,21 @@ func TestRBAC(t *testing.T) { t.Fatal(err) } require.Len(t, viewerUserPermissionsList.Data, 1) - require.Equal(t, "permission", viewerUserPermissionsList.Data[0].ObjectType) - require.Equal(t, viewPermission.ObjectId, viewerUserPermissionsList.Data[0].ObjectId) + require.Equal(t, "permission", viewerUserPermissionsList.Data[0].ResourceType) + require.Equal(t, viewPermission.ResourceId, viewerUserPermissionsList.Data[0].ResourceId) require.Equal(t, map[string]interface{}{ "name": "View Report", "description": "Permission to view reports", }, viewerUserPermissionsList.Data[0].Meta) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - Op: "delete", - ObjectType: viewPermission.ObjectType, - ObjectId: viewPermission.ObjectId, - Relation: "member", + Op: "delete", + ResourceType: viewPermission.ResourceType, + ResourceId: viewPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: viewerUser.ObjectType, - ObjectId: viewerUser.ObjectId, + ResourceType: viewerUser.ResourceType, + ResourceId: viewerUser.ResourceId, }, }) if err != nil { @@ -643,12 +643,12 @@ func TestRBAC(t *testing.T) { viewerUserHasPermission, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: viewPermission.ObjectType, - ObjectId: viewPermission.ObjectId, - Relation: "member", + ResourceType: viewPermission.ResourceType, + ResourceId: viewPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: viewerUser.ObjectType, - ObjectId: viewerUser.ObjectId, + ResourceType: viewerUser.ResourceType, + ResourceId: viewerUser.ResourceId, }, }, }, @@ -660,7 +660,7 @@ func TestRBAC(t *testing.T) { require.False(t, viewerUserHasPermission.Authorized()) viewerUserPermissionsList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ObjectId), + Query: fmt.Sprintf("select permission where user:%s is member", viewerUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -670,44 +670,44 @@ func TestRBAC(t *testing.T) { require.Empty(t, viewerUserPermissionsList.Data) // Clean up - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: adminUser.ObjectType, - ObjectId: adminUser.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: adminUser.ResourceType, + ResourceId: adminUser.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: viewerUser.ObjectType, - ObjectId: viewerUser.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: viewerUser.ResourceType, + ResourceId: viewerUser.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: adminRole.ObjectType, - ObjectId: adminRole.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: adminRole.ResourceType, + ResourceId: adminRole.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: viewerRole.ObjectType, - ObjectId: viewerRole.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: viewerRole.ResourceType, + ResourceId: viewerRole.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: createPermission.ObjectType, - ObjectId: createPermission.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: createPermission.ResourceType, + ResourceId: createPermission.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: viewPermission.ObjectType, - ObjectId: viewPermission.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: viewPermission.ResourceType, + ResourceId: viewPermission.ResourceId, }) if err != nil { t.Fatal(err) @@ -718,23 +718,23 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { setup() // Create users - freeUser, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", + freeUser, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", }) if err != nil { t.Fatal(err) } - paidUser, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", + paidUser, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", }) if err != nil { t.Fatal(err) } // Create pricing tiers - freeTier, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "pricing-tier", - ObjectId: "free", + freeTier, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "pricing-tier", + ResourceId: "free", Meta: map[string]interface{}{ "name": "Free Tier", }, @@ -742,18 +742,18 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { if err != nil { t.Fatal(err) } - paidTier, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "pricing-tier", - ObjectId: "paid", + paidTier, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "pricing-tier", + ResourceId: "paid", }) if err != nil { t.Fatal(err) } // Create features - customFeature, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "feature", - ObjectId: "custom", + customFeature, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "feature", + ResourceId: "custom", Meta: map[string]interface{}{ "name": "Custom Feature", }, @@ -761,16 +761,16 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { if err != nil { t.Fatal(err) } - feature1, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "feature", - ObjectId: "feature-1", + feature1, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "feature", + ResourceId: "feature-1", }) if err != nil { t.Fatal(err) } - feature2, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "feature", - ObjectId: "feature-2", + feature2, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "feature", + ResourceId: "feature-2", }) if err != nil { t.Fatal(err) @@ -780,12 +780,12 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { paidUserHasFeature, err := Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: customFeature.ObjectType, - ObjectId: customFeature.ObjectId, - Relation: "member", + ResourceType: customFeature.ResourceType, + ResourceId: customFeature.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: paidUser.ObjectType, - ObjectId: paidUser.ObjectId, + ResourceType: paidUser.ResourceType, + ResourceId: paidUser.ResourceId, }, }, }, @@ -797,7 +797,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.False(t, paidUserHasFeature.Authorized()) paidUserFeaturesList, err := Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ObjectId), + Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -807,12 +807,12 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.Empty(t, paidUserFeaturesList.Data) warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ - ObjectType: customFeature.ObjectType, - ObjectId: customFeature.ObjectId, - Relation: "member", + ResourceType: customFeature.ResourceType, + ResourceId: customFeature.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: paidUser.ObjectType, - ObjectId: paidUser.ObjectId, + ResourceType: paidUser.ResourceType, + ResourceId: paidUser.ResourceId, }, }) if err != nil { @@ -823,12 +823,12 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { paidUserHasFeature, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: customFeature.ObjectType, - ObjectId: customFeature.ObjectId, - Relation: "member", + ResourceType: customFeature.ResourceType, + ResourceId: customFeature.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: paidUser.ObjectType, - ObjectId: paidUser.ObjectId, + ResourceType: paidUser.ResourceType, + ResourceId: paidUser.ResourceId, }, }, }, @@ -840,7 +840,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.True(t, paidUserHasFeature.Authorized()) paidUserFeaturesList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ObjectId), + Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -848,20 +848,20 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { t.Fatal(err) } require.Len(t, paidUserFeaturesList.Data, 1) - require.Equal(t, "feature", paidUserFeaturesList.Data[0].ObjectType) - require.Equal(t, customFeature.ObjectId, paidUserFeaturesList.Data[0].ObjectId) + require.Equal(t, "feature", paidUserFeaturesList.Data[0].ResourceType) + require.Equal(t, customFeature.ResourceId, paidUserFeaturesList.Data[0].ResourceId) require.Equal(t, map[string]interface{}{ "name": "Custom Feature", }, paidUserFeaturesList.Data[0].Meta) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - Op: "delete", - ObjectType: customFeature.ObjectType, - ObjectId: customFeature.ObjectId, - Relation: "member", + Op: "delete", + ResourceType: customFeature.ResourceType, + ResourceId: customFeature.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: paidUser.ObjectType, - ObjectId: paidUser.ObjectId, + ResourceType: paidUser.ResourceType, + ResourceId: paidUser.ResourceId, }, }) if err != nil { @@ -872,12 +872,12 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { paidUserHasFeature, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: customFeature.ObjectType, - ObjectId: customFeature.ObjectId, - Relation: "member", + ResourceType: customFeature.ResourceType, + ResourceId: customFeature.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: paidUser.ObjectType, - ObjectId: paidUser.ObjectId, + ResourceType: paidUser.ResourceType, + ResourceId: paidUser.ResourceId, }, }, }, @@ -889,7 +889,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.False(t, paidUserHasFeature.Authorized()) paidUserFeaturesList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ObjectId), + Query: fmt.Sprintf("select feature where user:%s is member", paidUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -902,12 +902,12 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { freeUserHasFeature, err := Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: feature1.ObjectType, - ObjectId: feature1.ObjectId, - Relation: "member", + ResourceType: feature1.ResourceType, + ResourceId: feature1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: freeUser.ObjectType, - ObjectId: freeUser.ObjectId, + ResourceType: freeUser.ResourceType, + ResourceId: freeUser.ResourceId, }, }, }, @@ -919,7 +919,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.False(t, freeUserHasFeature.Authorized()) freeUserFeaturesList, err := Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ObjectId), + Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -929,7 +929,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.Empty(t, freeUserFeaturesList.Data) featureUserTiersList, err := Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select pricing-tier where user:%s is member", freeUser.ObjectId), + Query: fmt.Sprintf("select pricing-tier where user:%s is member", freeUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -939,12 +939,12 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.Empty(t, featureUserTiersList.Data) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - ObjectType: feature1.ObjectType, - ObjectId: feature1.ObjectId, - Relation: "member", + ResourceType: feature1.ResourceType, + ResourceId: feature1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: freeTier.ObjectType, - ObjectId: freeTier.ObjectId, + ResourceType: freeTier.ResourceType, + ResourceId: freeTier.ResourceId, }, }) if err != nil { @@ -953,12 +953,12 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - ObjectType: freeTier.ObjectType, - ObjectId: freeTier.ObjectId, - Relation: "member", + ResourceType: freeTier.ResourceType, + ResourceId: freeTier.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: freeUser.ObjectType, - ObjectId: freeUser.ObjectId, + ResourceType: freeUser.ResourceType, + ResourceId: freeUser.ResourceId, }, }) if err != nil { @@ -969,12 +969,12 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { freeUserHasFeature, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: feature1.ObjectType, - ObjectId: feature1.ObjectId, - Relation: "member", + ResourceType: feature1.ResourceType, + ResourceId: feature1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: freeUser.ObjectType, - ObjectId: freeUser.ObjectId, + ResourceType: freeUser.ResourceType, + ResourceId: freeUser.ResourceId, }, }, }, @@ -986,7 +986,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.True(t, freeUserHasFeature.Authorized()) freeUserFeaturesList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ObjectId), + Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -994,12 +994,12 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { t.Fatal(err) } require.Len(t, freeUserFeaturesList.Data, 1) - require.Equal(t, "feature", freeUserFeaturesList.Data[0].ObjectType) - require.Equal(t, feature1.ObjectId, freeUserFeaturesList.Data[0].ObjectId) + require.Equal(t, "feature", freeUserFeaturesList.Data[0].ResourceType) + require.Equal(t, feature1.ResourceId, freeUserFeaturesList.Data[0].ResourceId) require.Empty(t, freeUserFeaturesList.Data[0].Meta) featureUserTiersList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select pricing-tier where user:%s is member", freeUser.ObjectId), + Query: fmt.Sprintf("select pricing-tier where user:%s is member", freeUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -1007,20 +1007,20 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { t.Fatal(err) } require.Len(t, featureUserTiersList.Data, 1) - require.Equal(t, "pricing-tier", featureUserTiersList.Data[0].ObjectType) - require.Equal(t, freeTier.ObjectId, featureUserTiersList.Data[0].ObjectId) + require.Equal(t, "pricing-tier", featureUserTiersList.Data[0].ResourceType) + require.Equal(t, freeTier.ResourceId, featureUserTiersList.Data[0].ResourceId) require.Equal(t, map[string]interface{}{ "name": "Free Tier", }, featureUserTiersList.Data[0].Meta) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - Op: "delete", - ObjectType: feature1.ObjectType, - ObjectId: feature1.ObjectId, - Relation: "member", + Op: "delete", + ResourceType: feature1.ResourceType, + ResourceId: feature1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: freeTier.ObjectType, - ObjectId: freeTier.ObjectId, + ResourceType: freeTier.ResourceType, + ResourceId: freeTier.ResourceId, }, }) if err != nil { @@ -1029,13 +1029,13 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - Op: "delete", - ObjectType: freeTier.ObjectType, - ObjectId: freeTier.ObjectId, - Relation: "member", + Op: "delete", + ResourceType: freeTier.ResourceType, + ResourceId: freeTier.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: freeUser.ObjectType, - ObjectId: freeUser.ObjectId, + ResourceType: freeUser.ResourceType, + ResourceId: freeUser.ResourceId, }, }) if err != nil { @@ -1046,12 +1046,12 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { freeUserHasFeature, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: feature1.ObjectType, - ObjectId: feature1.ObjectId, - Relation: "member", + ResourceType: feature1.ResourceType, + ResourceId: feature1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: freeUser.ObjectType, - ObjectId: freeUser.ObjectId, + ResourceType: freeUser.ResourceType, + ResourceId: freeUser.ResourceId, }, }, }, @@ -1063,7 +1063,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.False(t, freeUserHasFeature.Authorized()) freeUserFeaturesList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ObjectId), + Query: fmt.Sprintf("select feature where user:%s is member", freeUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -1073,7 +1073,7 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.Empty(t, freeUserFeaturesList.Data) featureUserTiersList, err = Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select pricing-tier where user:%s is member", freeUser.ObjectId), + Query: fmt.Sprintf("select pricing-tier where user:%s is member", freeUser.ResourceId), Limit: 10, WarrantToken: "latest", }) @@ -1083,51 +1083,51 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { require.Empty(t, featureUserTiersList.Data) // Clean up - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: freeUser.ObjectType, - ObjectId: freeUser.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: freeUser.ResourceType, + ResourceId: freeUser.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: paidUser.ObjectType, - ObjectId: paidUser.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: paidUser.ResourceType, + ResourceId: paidUser.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: freeTier.ObjectType, - ObjectId: freeTier.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: freeTier.ResourceType, + ResourceId: freeTier.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: paidTier.ObjectType, - ObjectId: paidTier.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: paidTier.ResourceType, + ResourceId: paidTier.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: customFeature.ObjectType, - ObjectId: customFeature.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: customFeature.ResourceType, + ResourceId: customFeature.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: feature1.ObjectType, - ObjectId: feature1.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: feature1.ResourceType, + ResourceId: feature1.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: feature2.ObjectType, - ObjectId: feature2.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: feature2.ResourceType, + ResourceId: feature2.ResourceId, }) if err != nil { t.Fatal(err) @@ -1137,23 +1137,23 @@ func TestPricingTiersFeaturesAndUsers(t *testing.T) { func TestWarrants(t *testing.T) { setup() - user1, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", - ObjectId: "userA", + user1, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", + ResourceId: "userA", }) if err != nil { t.Fatal(err) } - user2, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", - ObjectId: "userB", + user2, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", + ResourceId: "userB", }) if err != nil { t.Fatal(err) } - newPermission, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "permission", - ObjectId: "perm1", + newPermission, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "permission", + ResourceId: "perm1", Meta: map[string]interface{}{ "name": "Permission 1", "description": "Permission 1", @@ -1166,12 +1166,12 @@ func TestWarrants(t *testing.T) { userHasPermission, err := Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: newPermission.ObjectType, - ObjectId: newPermission.ObjectId, - Relation: "member", + ResourceType: newPermission.ResourceType, + ResourceId: newPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + ResourceType: user1.ResourceType, + ResourceId: user1.ResourceId, }, }, }, @@ -1183,12 +1183,12 @@ func TestWarrants(t *testing.T) { require.False(t, userHasPermission.Authorized()) warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ - ObjectType: newPermission.ObjectType, - ObjectId: newPermission.ObjectId, - Relation: "member", + ResourceType: newPermission.ResourceType, + ResourceId: newPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + ResourceType: user1.ResourceType, + ResourceId: user1.ResourceId, }, }) if err != nil { @@ -1197,12 +1197,12 @@ func TestWarrants(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - ObjectType: newPermission.ObjectType, - ObjectId: newPermission.ObjectId, - Relation: "member", + ResourceType: newPermission.ResourceType, + ResourceId: newPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: user2.ObjectType, - ObjectId: user2.ObjectId, + ResourceType: user2.ResourceType, + ResourceId: user2.ResourceId, }, }) if err != nil { @@ -1218,11 +1218,11 @@ func TestWarrants(t *testing.T) { t.Fatal(err) } require.Len(t, warrants1.Data, 1) - require.Equal(t, newPermission.ObjectType, warrants1.Data[0].ObjectType) - require.Equal(t, newPermission.ObjectId, warrants1.Data[0].ObjectId) + require.Equal(t, newPermission.ResourceType, warrants1.Data[0].ResourceType) + require.Equal(t, newPermission.ResourceId, warrants1.Data[0].ResourceId) require.Equal(t, "member", warrants1.Data[0].Relation) - require.Equal(t, user2.ObjectType, warrants1.Data[0].Subject.ObjectType) - require.Equal(t, user2.ObjectId, warrants1.Data[0].Subject.ObjectId) + require.Equal(t, user2.ResourceType, warrants1.Data[0].Subject.ResourceType) + require.Equal(t, user2.ResourceId, warrants1.Data[0].Subject.ResourceId) warrants2, err := ListWarrants(context.Background(), ListWarrantsOpts{ Limit: 1, @@ -1233,36 +1233,36 @@ func TestWarrants(t *testing.T) { t.Fatal(err) } require.Len(t, warrants2.Data, 1) - require.Equal(t, newPermission.ObjectType, warrants2.Data[0].ObjectType) - require.Equal(t, newPermission.ObjectId, warrants2.Data[0].ObjectId) + require.Equal(t, newPermission.ResourceType, warrants2.Data[0].ResourceType) + require.Equal(t, newPermission.ResourceId, warrants2.Data[0].ResourceId) require.Equal(t, "member", warrants2.Data[0].Relation) - require.Equal(t, user1.ObjectType, warrants2.Data[0].Subject.ObjectType) - require.Equal(t, user1.ObjectId, warrants2.Data[0].Subject.ObjectId) + require.Equal(t, user1.ResourceType, warrants2.Data[0].Subject.ResourceType) + require.Equal(t, user1.ResourceId, warrants2.Data[0].Subject.ResourceId) warrants3, err := ListWarrants(context.Background(), ListWarrantsOpts{ SubjectType: "user", - SubjectId: user1.ObjectId, + SubjectId: user1.ResourceId, WarrantToken: "latest", }) if err != nil { t.Fatal(err) } require.Len(t, warrants3.Data, 1) - require.Equal(t, newPermission.ObjectType, warrants3.Data[0].ObjectType) - require.Equal(t, newPermission.ObjectId, warrants3.Data[0].ObjectId) + require.Equal(t, newPermission.ResourceType, warrants3.Data[0].ResourceType) + require.Equal(t, newPermission.ResourceId, warrants3.Data[0].ResourceId) require.Equal(t, "member", warrants3.Data[0].Relation) - require.Equal(t, user1.ObjectType, warrants3.Data[0].Subject.ObjectType) - require.Equal(t, user1.ObjectId, warrants3.Data[0].Subject.ObjectId) + require.Equal(t, user1.ResourceType, warrants3.Data[0].Subject.ResourceType) + require.Equal(t, user1.ResourceId, warrants3.Data[0].Subject.ResourceId) userHasPermission, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: newPermission.ObjectType, - ObjectId: newPermission.ObjectId, - Relation: "member", + ResourceType: newPermission.ResourceType, + ResourceId: newPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + ResourceType: user1.ResourceType, + ResourceId: user1.ResourceId, }, }, }, @@ -1274,25 +1274,25 @@ func TestWarrants(t *testing.T) { require.True(t, userHasPermission.Authorized()) queryResponse, err := Query(context.Background(), QueryOpts{ - Query: fmt.Sprintf("select permission where user:%s is member", user1.ObjectId), + Query: fmt.Sprintf("select permission where user:%s is member", user1.ResourceId), WarrantToken: "latest", }) if err != nil { t.Fatal(err) } require.Len(t, queryResponse.Data, 1) - require.Equal(t, newPermission.ObjectType, queryResponse.Data[0].ObjectType) - require.Equal(t, newPermission.ObjectId, queryResponse.Data[0].ObjectId) + require.Equal(t, newPermission.ResourceType, queryResponse.Data[0].ResourceType) + require.Equal(t, newPermission.ResourceId, queryResponse.Data[0].ResourceId) require.Equal(t, "member", queryResponse.Data[0].Relation) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - Op: "delete", - ObjectType: newPermission.ObjectType, - ObjectId: newPermission.ObjectId, - Relation: "member", + Op: "delete", + ResourceType: newPermission.ResourceType, + ResourceId: newPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + ResourceType: user1.ResourceType, + ResourceId: user1.ResourceId, }, }) if err != nil { @@ -1303,12 +1303,12 @@ func TestWarrants(t *testing.T) { userHasPermission, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: newPermission.ObjectType, - ObjectId: newPermission.ObjectId, - Relation: "member", + ResourceType: newPermission.ResourceType, + ResourceId: newPermission.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + ResourceType: user1.ResourceType, + ResourceId: user1.ResourceId, }, }, }, @@ -1320,23 +1320,23 @@ func TestWarrants(t *testing.T) { require.False(t, userHasPermission.Authorized()) // Clean up - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: user1.ObjectType, - ObjectId: user1.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: user1.ResourceType, + ResourceId: user1.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: user2.ObjectType, - ObjectId: user2.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: user2.ResourceType, + ResourceId: user2.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: newPermission.ObjectType, - ObjectId: newPermission.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: newPermission.ResourceType, + ResourceId: newPermission.ResourceId, }) if err != nil { t.Fatal(err) @@ -1346,15 +1346,15 @@ func TestWarrants(t *testing.T) { func TestBatchWarrants(t *testing.T) { setup() - newUser, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", + newUser, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", }) if err != nil { t.Fatal(err) } - permission1, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "permission", - ObjectId: "perm1", + permission1, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "permission", + ResourceId: "perm1", Meta: map[string]interface{}{ "name": "Permission 1", "description": "Permission 1", @@ -1363,9 +1363,9 @@ func TestBatchWarrants(t *testing.T) { if err != nil { t.Fatal(err) } - permission2, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "permission", - ObjectId: "perm2", + permission2, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "permission", + ResourceId: "perm2", Meta: map[string]interface{}{ "name": "Permission 2", "description": "Permission 2", @@ -1378,21 +1378,21 @@ func TestBatchWarrants(t *testing.T) { userHasPermissions, err := CheckBatch(context.Background(), CheckBatchOpts{ Checks: []WarrantCheck{ { - ObjectType: permission1.ObjectType, - ObjectId: permission1.ObjectId, - Relation: "member", + ResourceType: permission1.ResourceType, + ResourceId: permission1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }, }, { - ObjectType: permission2.ObjectType, - ObjectId: permission2.ObjectId, - Relation: "member", + ResourceType: permission2.ResourceType, + ResourceId: permission2.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }, }, }, @@ -1407,22 +1407,22 @@ func TestBatchWarrants(t *testing.T) { warrantResponse, err := BatchWriteWarrants(context.Background(), []WriteWarrantOpts{ { - ObjectType: permission1.ObjectType, - ObjectId: permission1.ObjectId, - Relation: "member", + ResourceType: permission1.ResourceType, + ResourceId: permission1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }, }, { - Op: "create", - ObjectType: permission2.ObjectType, - ObjectId: permission2.ObjectId, - Relation: "member", + Op: "create", + ResourceType: permission2.ResourceType, + ResourceId: permission2.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }, }, }) @@ -1434,21 +1434,21 @@ func TestBatchWarrants(t *testing.T) { userHasPermissions, err = CheckBatch(context.Background(), CheckBatchOpts{ Checks: []WarrantCheck{ { - ObjectType: permission1.ObjectType, - ObjectId: permission1.ObjectId, - Relation: "member", + ResourceType: permission1.ResourceType, + ResourceId: permission1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }, }, { - ObjectType: permission2.ObjectType, - ObjectId: permission2.ObjectId, - Relation: "member", + ResourceType: permission2.ResourceType, + ResourceId: permission2.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }, }, }, @@ -1463,23 +1463,23 @@ func TestBatchWarrants(t *testing.T) { warrantResponse, err = BatchWriteWarrants(context.Background(), []WriteWarrantOpts{ { - Op: "delete", - ObjectType: permission1.ObjectType, - ObjectId: permission1.ObjectId, - Relation: "member", + Op: "delete", + ResourceType: permission1.ResourceType, + ResourceId: permission1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }, }, { - Op: "delete", - ObjectType: permission2.ObjectType, - ObjectId: permission2.ObjectId, - Relation: "member", + Op: "delete", + ResourceType: permission2.ResourceType, + ResourceId: permission2.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }, }, }) @@ -1491,21 +1491,21 @@ func TestBatchWarrants(t *testing.T) { userHasPermissions, err = CheckBatch(context.Background(), CheckBatchOpts{ Checks: []WarrantCheck{ { - ObjectType: permission1.ObjectType, - ObjectId: permission1.ObjectId, - Relation: "member", + ResourceType: permission1.ResourceType, + ResourceId: permission1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }, }, { - ObjectType: permission2.ObjectType, - ObjectId: permission2.ObjectId, - Relation: "member", + ResourceType: permission2.ResourceType, + ResourceId: permission2.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }, }, }, @@ -1519,23 +1519,23 @@ func TestBatchWarrants(t *testing.T) { require.False(t, userHasPermissions[1].Authorized()) // Clean up - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: newUser.ObjectType, - ObjectId: newUser.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: newUser.ResourceType, + ResourceId: newUser.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: permission1.ObjectType, - ObjectId: permission1.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: permission1.ResourceType, + ResourceId: permission1.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: permission2.ObjectType, - ObjectId: permission2.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: permission2.ResourceType, + ResourceId: permission2.ResourceId, }) if err != nil { t.Fatal(err) @@ -1546,12 +1546,12 @@ func TestWarrantsWithPolicy(t *testing.T) { setup() warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ - ObjectType: "permission", - ObjectId: "test-permission", - Relation: "member", + ResourceType: "permission", + ResourceId: "test-permission", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user-1", + ResourceType: "user", + ResourceId: "user-1", }, Policy: `geo == "us"`, }) @@ -1563,12 +1563,12 @@ func TestWarrantsWithPolicy(t *testing.T) { checkResult, err := Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: "permission", - ObjectId: "test-permission", - Relation: "member", + ResourceType: "permission", + ResourceId: "test-permission", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user-1", + ResourceType: "user", + ResourceId: "user-1", }, Context: map[string]interface{}{ "geo": "us", @@ -1585,12 +1585,12 @@ func TestWarrantsWithPolicy(t *testing.T) { checkResult, err = Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: "permission", - ObjectId: "test-permission", - Relation: "member", + ResourceType: "permission", + ResourceId: "test-permission", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user-1", + ResourceType: "user", + ResourceId: "user-1", }, Context: map[string]interface{}{ "geo": "eu", @@ -1605,13 +1605,13 @@ func TestWarrantsWithPolicy(t *testing.T) { require.False(t, checkResult.Authorized()) warrantResponse, err = WriteWarrant(context.Background(), WriteWarrantOpts{ - Op: "delete", - ObjectType: "permission", - ObjectId: "test-permission", - Relation: "member", + Op: "delete", + ResourceType: "permission", + ResourceId: "test-permission", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user-1", + ResourceType: "user", + ResourceId: "user-1", }, Policy: `geo == "us"`, }) @@ -1621,16 +1621,16 @@ func TestWarrantsWithPolicy(t *testing.T) { require.NotEmpty(t, warrantResponse.WarrantToken) // Clean up - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: "permission", - ObjectId: "test-permission", + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: "permission", + ResourceId: "test-permission", }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: "user", - ObjectId: "user-1", + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: "user", + ResourceId: "user-1", }) if err != nil { t.Fatal(err) @@ -1640,23 +1640,23 @@ func TestWarrantsWithPolicy(t *testing.T) { func TestQueryWarrants(t *testing.T) { setup() - userA, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", - ObjectId: "userA", + userA, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", + ResourceId: "userA", }) if err != nil { t.Fatal(err) } - userB, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "user", - ObjectId: "userB", + userB, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "user", + ResourceId: "userB", }) if err != nil { t.Fatal(err) } - permission1, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "permission", - ObjectId: "perm1", + permission1, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "permission", + ResourceId: "perm1", Meta: map[string]interface{}{ "name": "Permission 1", "description": "This is permission 1.", @@ -1665,16 +1665,16 @@ func TestQueryWarrants(t *testing.T) { if err != nil { t.Fatal(err) } - permission2, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "permission", - ObjectId: "perm2", + permission2, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "permission", + ResourceId: "perm2", }) if err != nil { t.Fatal(err) } - permission3, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "permission", - ObjectId: "perm3", + permission3, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "permission", + ResourceId: "perm3", Meta: map[string]interface{}{ "name": "Permission 3", "description": "This is permission 3.", @@ -1683,9 +1683,9 @@ func TestQueryWarrants(t *testing.T) { if err != nil { t.Fatal(err) } - role1, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "role", - ObjectId: "role1", + role1, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "role", + ResourceId: "role1", Meta: map[string]interface{}{ "name": "Role 1", "description": "This is role 1.", @@ -1694,9 +1694,9 @@ func TestQueryWarrants(t *testing.T) { if err != nil { t.Fatal(err) } - role2, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "role", - ObjectId: "role2", + role2, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "role", + ResourceId: "role2", Meta: map[string]interface{}{ "name": "Role 2", }, @@ -1707,67 +1707,67 @@ func TestQueryWarrants(t *testing.T) { warrantResponse, err := BatchWriteWarrants(context.Background(), []WriteWarrantOpts{ { - ObjectType: permission1.ObjectType, - ObjectId: permission1.ObjectId, - Relation: "member", + ResourceType: permission1.ResourceType, + ResourceId: permission1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: role1.ObjectType, - ObjectId: role1.ObjectId, + ResourceType: role1.ResourceType, + ResourceId: role1.ResourceId, }, }, { - ObjectType: permission2.ObjectType, - ObjectId: permission2.ObjectId, - Relation: "member", + ResourceType: permission2.ResourceType, + ResourceId: permission2.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: role2.ObjectType, - ObjectId: role2.ObjectId, + ResourceType: role2.ResourceType, + ResourceId: role2.ResourceId, }, }, { - ObjectType: permission3.ObjectType, - ObjectId: permission3.ObjectId, - Relation: "member", + ResourceType: permission3.ResourceType, + ResourceId: permission3.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: role2.ObjectType, - ObjectId: role2.ObjectId, + ResourceType: role2.ResourceType, + ResourceId: role2.ResourceId, }, }, { - ObjectType: role2.ObjectType, - ObjectId: role2.ObjectId, - Relation: "member", + ResourceType: role2.ResourceType, + ResourceId: role2.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: role1.ObjectType, - ObjectId: role1.ObjectId, + ResourceType: role1.ResourceType, + ResourceId: role1.ResourceId, }, }, { - ObjectType: permission1.ObjectType, - ObjectId: permission1.ObjectId, - Relation: "member", + ResourceType: permission1.ResourceType, + ResourceId: permission1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: role2.ObjectType, - ObjectId: role2.ObjectId, + ResourceType: role2.ResourceType, + ResourceId: role2.ResourceId, }, Policy: "tenantId == 123", }, { - ObjectType: role1.ObjectType, - ObjectId: role1.ObjectId, - Relation: "member", + ResourceType: role1.ResourceType, + ResourceId: role1.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: userA.ObjectType, - ObjectId: userA.ObjectId, + ResourceType: userA.ResourceType, + ResourceId: userA.ResourceId, }, }, { - ObjectType: role2.ObjectType, - ObjectId: role2.ObjectId, - Relation: "member", + ResourceType: role2.ResourceType, + ResourceId: role2.ResourceId, + Relation: "member", Subject: Subject{ - ObjectType: userB.ObjectType, - ObjectId: userB.ObjectId, + ResourceType: userB.ResourceType, + ResourceId: userB.ResourceId, }, }, }) @@ -1786,14 +1786,14 @@ func TestQueryWarrants(t *testing.T) { t.Fatal(err) } require.Len(t, queryResponse.Data, 1) - require.Equal(t, role1.ObjectType, queryResponse.Data[0].ObjectType) - require.Equal(t, role1.ObjectId, queryResponse.Data[0].ObjectId) + require.Equal(t, role1.ResourceType, queryResponse.Data[0].ResourceType) + require.Equal(t, role1.ResourceId, queryResponse.Data[0].ResourceId) require.Equal(t, "member", queryResponse.Data[0].Relation) - require.Equal(t, role1.ObjectType, queryResponse.Data[0].Warrant.ObjectType) - require.Equal(t, role1.ObjectId, queryResponse.Data[0].Warrant.ObjectId) + require.Equal(t, role1.ResourceType, queryResponse.Data[0].Warrant.ResourceType) + require.Equal(t, role1.ResourceId, queryResponse.Data[0].Warrant.ResourceId) require.Equal(t, "member", queryResponse.Data[0].Warrant.Relation) - require.Equal(t, userA.ObjectType, queryResponse.Data[0].Warrant.Subject.ObjectType) - require.Equal(t, userA.ObjectId, queryResponse.Data[0].Warrant.Subject.ObjectId) + require.Equal(t, userA.ResourceType, queryResponse.Data[0].Warrant.Subject.ResourceType) + require.Equal(t, userA.ResourceId, queryResponse.Data[0].Warrant.Subject.ResourceId) require.Empty(t, queryResponse.Data[0].Warrant.Policy) require.False(t, queryResponse.Data[0].IsImplicit) @@ -1808,14 +1808,14 @@ func TestQueryWarrants(t *testing.T) { t.Fatal(err) } require.Len(t, queryResponse.Data, 1) - require.Equal(t, role2.ObjectType, queryResponse.Data[0].ObjectType) - require.Equal(t, role2.ObjectId, queryResponse.Data[0].ObjectId) + require.Equal(t, role2.ResourceType, queryResponse.Data[0].ResourceType) + require.Equal(t, role2.ResourceId, queryResponse.Data[0].ResourceId) require.Equal(t, "member", queryResponse.Data[0].Relation) - require.Equal(t, role2.ObjectType, queryResponse.Data[0].Warrant.ObjectType) - require.Equal(t, role2.ObjectId, queryResponse.Data[0].Warrant.ObjectId) + require.Equal(t, role2.ResourceType, queryResponse.Data[0].Warrant.ResourceType) + require.Equal(t, role2.ResourceId, queryResponse.Data[0].Warrant.ResourceId) require.Equal(t, "member", queryResponse.Data[0].Warrant.Relation) - require.Equal(t, role1.ObjectType, queryResponse.Data[0].Warrant.Subject.ObjectType) - require.Equal(t, role1.ObjectId, queryResponse.Data[0].Warrant.Subject.ObjectId) + require.Equal(t, role1.ResourceType, queryResponse.Data[0].Warrant.Subject.ResourceType) + require.Equal(t, role1.ResourceId, queryResponse.Data[0].Warrant.Subject.ResourceId) require.Empty(t, queryResponse.Data[0].Warrant.Policy) require.True(t, queryResponse.Data[0].IsImplicit) @@ -1831,62 +1831,62 @@ func TestQueryWarrants(t *testing.T) { t.Fatal(err) } require.Len(t, queryResponse.Data, 3) - require.Equal(t, permission1.ObjectType, queryResponse.Data[0].ObjectType) - require.Equal(t, permission1.ObjectId, queryResponse.Data[0].ObjectId) + require.Equal(t, permission1.ResourceType, queryResponse.Data[0].ResourceType) + require.Equal(t, permission1.ResourceId, queryResponse.Data[0].ResourceId) require.Equal(t, "member", queryResponse.Data[0].Relation) - require.Equal(t, permission2.ObjectType, queryResponse.Data[1].ObjectType) - require.Equal(t, permission2.ObjectId, queryResponse.Data[1].ObjectId) + require.Equal(t, permission2.ResourceType, queryResponse.Data[1].ResourceType) + require.Equal(t, permission2.ResourceId, queryResponse.Data[1].ResourceId) require.Equal(t, "member", queryResponse.Data[1].Relation) - require.Equal(t, permission3.ObjectType, queryResponse.Data[2].ObjectType) - require.Equal(t, permission3.ObjectId, queryResponse.Data[2].ObjectId) + require.Equal(t, permission3.ResourceType, queryResponse.Data[2].ResourceType) + require.Equal(t, permission3.ResourceId, queryResponse.Data[2].ResourceId) require.Equal(t, "member", queryResponse.Data[2].Relation) // Clean up - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: role1.ObjectType, - ObjectId: role1.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: role1.ResourceType, + ResourceId: role1.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: role2.ObjectType, - ObjectId: role2.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: role2.ResourceType, + ResourceId: role2.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: permission1.ObjectType, - ObjectId: permission1.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: permission1.ResourceType, + ResourceId: permission1.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: permission2.ObjectType, - ObjectId: permission2.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: permission2.ResourceType, + ResourceId: permission2.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: permission3.ObjectType, - ObjectId: permission3.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: permission3.ResourceType, + ResourceId: permission3.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: userA.ObjectType, - ObjectId: userA.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: userA.ResourceType, + ResourceId: userA.ResourceId, }) if err != nil { t.Fatal(err) } - err = DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: userB.ObjectType, - ObjectId: userB.ObjectId, + err = DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: userB.ResourceType, + ResourceId: userB.ResourceId, }) if err != nil { t.Fatal(err) diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go index b03cb1e1..4a165078 100644 --- a/pkg/fga/client_test.go +++ b/pkg/fga/client_test.go @@ -13,12 +13,12 @@ import ( "github.com/workos/workos-go/v4/pkg/common" ) -func TestGetObject(t *testing.T) { +func TestGetResource(t *testing.T) { tests := []struct { scenario string client *Client - options GetObjectOpts - expected Object + options GetResourceOpts + expected Resource err bool }{ { @@ -27,51 +27,51 @@ func TestGetObject(t *testing.T) { err: true, }, { - scenario: "Request returns an Object", + scenario: "Request returns an Resource", client: &Client{ APIKey: "test", }, - options: GetObjectOpts{ - ObjectType: "report", - ObjectId: "ljc_1029", + options: GetResourceOpts{ + ResourceType: "report", + ResourceId: "ljc_1029", }, - expected: Object{ - ObjectType: "report", - ObjectId: "ljc_1029", + expected: Resource{ + ResourceType: "report", + ResourceId: "ljc_1029", }, }, } for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(getObjectTestHandler)) + server := httptest.NewServer(http.HandlerFunc(getResourceTestHandler)) defer server.Close() client := test.client client.Endpoint = server.URL client.HTTPClient = server.Client() - object, err := client.GetObject(context.Background(), test.options) + resource, err := client.GetResource(context.Background(), test.options) if test.err { require.Error(t, err) return } require.NoError(t, err) - require.Equal(t, test.expected, object) + require.Equal(t, test.expected, resource) }) } } -func getObjectTestHandler(w http.ResponseWriter, r *http.Request) { +func getResourceTestHandler(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if auth != "Bearer test" { http.Error(w, "bad auth", http.StatusUnauthorized) return } - body, err := json.Marshal(Object{ - ObjectType: "report", - ObjectId: "ljc_1029", + body, err := json.Marshal(Resource{ + ResourceType: "report", + ResourceId: "ljc_1029", }) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -82,12 +82,12 @@ func getObjectTestHandler(w http.ResponseWriter, r *http.Request) { w.Write(body) } -func TestListObjects(t *testing.T) { +func TestListResources(t *testing.T) { tests := []struct { scenario string client *Client - options ListObjectsOpts - expected ListObjectsResponse + options ListResourcesOpts + expected ListResourcesResponse err bool }{ { @@ -96,23 +96,23 @@ func TestListObjects(t *testing.T) { err: true, }, { - scenario: "Request returns Objects", + scenario: "Request returns Resources", client: &Client{ APIKey: "test", }, - options: ListObjectsOpts{ - ObjectType: "report", + options: ListResourcesOpts{ + ResourceType: "report", }, - expected: ListObjectsResponse{ - Data: []Object{ + expected: ListResourcesResponse{ + Data: []Resource{ { - ObjectType: "report", - ObjectId: "ljc_1029", + ResourceType: "report", + ResourceId: "ljc_1029", }, { - ObjectType: "report", - ObjectId: "mso_0806", + ResourceType: "report", + ResourceId: "mso_0806", }, }, ListMetadata: common.ListMetadata{ @@ -125,25 +125,25 @@ func TestListObjects(t *testing.T) { for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(listObjectsTestHandler)) + server := httptest.NewServer(http.HandlerFunc(listResourcesTestHandler)) defer server.Close() client := test.client client.Endpoint = server.URL client.HTTPClient = server.Client() - objects, err := client.ListObjects(context.Background(), test.options) + resources, err := client.ListResources(context.Background(), test.options) if test.err { require.Error(t, err) return } require.NoError(t, err) - require.Equal(t, test.expected, objects) + require.Equal(t, test.expected, resources) }) } } -func listObjectsTestHandler(w http.ResponseWriter, r *http.Request) { +func listResourcesTestHandler(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if auth != "Bearer test" { http.Error(w, "bad auth", http.StatusUnauthorized) @@ -156,17 +156,17 @@ func listObjectsTestHandler(w http.ResponseWriter, r *http.Request) { } body, err := json.Marshal(struct { - ListObjectsResponse + ListResourcesResponse }{ - ListObjectsResponse: ListObjectsResponse{ - Data: []Object{ + ListResourcesResponse: ListResourcesResponse{ + Data: []Resource{ { - ObjectType: "report", - ObjectId: "ljc_1029", + ResourceType: "report", + ResourceId: "ljc_1029", }, { - ObjectType: "report", - ObjectId: "mso_0806", + ResourceType: "report", + ResourceId: "mso_0806", }, }, ListMetadata: common.ListMetadata{ @@ -184,12 +184,12 @@ func listObjectsTestHandler(w http.ResponseWriter, r *http.Request) { w.Write(body) } -func TestListObjectTypes(t *testing.T) { +func TestListResourceTypes(t *testing.T) { tests := []struct { scenario string client *Client - options ListObjectTypesOpts - expected ListObjectTypesResponse + options ListResourceTypesOpts + expected ListResourceTypesResponse err bool }{ { @@ -198,16 +198,16 @@ func TestListObjectTypes(t *testing.T) { err: true, }, { - scenario: "Request returns ObjectTypes", + scenario: "Request returns ResourceTypes", client: &Client{ APIKey: "test", }, - options: ListObjectTypesOpts{ + options: ListResourceTypesOpts{ Order: "asc", }, - expected: ListObjectTypesResponse{ - Data: []ObjectType{ + expected: ListResourceTypesResponse{ + Data: []ResourceType{ { Type: "report", Relations: map[string]interface{}{ @@ -235,25 +235,25 @@ func TestListObjectTypes(t *testing.T) { for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(listObjectTypesTestHandler)) + server := httptest.NewServer(http.HandlerFunc(listResourceTypesTestHandler)) defer server.Close() client := test.client client.Endpoint = server.URL client.HTTPClient = server.Client() - objectTypes, err := client.ListObjectTypes(context.Background(), test.options) + resourceTypes, err := client.ListResourceTypes(context.Background(), test.options) if test.err { require.Error(t, err) return } require.NoError(t, err) - require.Equal(t, test.expected, objectTypes) + require.Equal(t, test.expected, resourceTypes) }) } } -func listObjectTypesTestHandler(w http.ResponseWriter, r *http.Request) { +func listResourceTypesTestHandler(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if auth != "Bearer test" { http.Error(w, "bad auth", http.StatusUnauthorized) @@ -266,10 +266,10 @@ func listObjectTypesTestHandler(w http.ResponseWriter, r *http.Request) { } body, err := json.Marshal(struct { - ListObjectTypesResponse + ListResourceTypesResponse }{ - ListObjectTypesResponse: ListObjectTypesResponse{ - Data: []ObjectType{ + ListResourceTypesResponse: ListResourceTypesResponse{ + Data: []ResourceType{ { Type: "report", Relations: map[string]interface{}{ @@ -302,12 +302,12 @@ func listObjectTypesTestHandler(w http.ResponseWriter, r *http.Request) { w.Write(body) } -func TestBatchUpdateObjectTypes(t *testing.T) { +func TestBatchUpdateResourceTypes(t *testing.T) { tests := []struct { scenario string client *Client - options []UpdateObjectTypeOpts - expected []ObjectType + options []UpdateResourceTypeOpts + expected []ResourceType err bool }{ { @@ -316,11 +316,11 @@ func TestBatchUpdateObjectTypes(t *testing.T) { err: true, }, { - scenario: "Request returns ObjectTypes", + scenario: "Request returns ResourceTypes", client: &Client{ APIKey: "test", }, - options: []UpdateObjectTypeOpts{ + options: []UpdateResourceTypeOpts{ { Type: "report", Relations: map[string]interface{}{ @@ -339,7 +339,7 @@ func TestBatchUpdateObjectTypes(t *testing.T) { }, }, - expected: []ObjectType{ + expected: []ResourceType{ { Type: "report", Relations: map[string]interface{}{ @@ -362,25 +362,25 @@ func TestBatchUpdateObjectTypes(t *testing.T) { for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(batchUpdateObjectTypesTestHandler)) + server := httptest.NewServer(http.HandlerFunc(batchUpdateResourceTypesTestHandler)) defer server.Close() client := test.client client.Endpoint = server.URL client.HTTPClient = server.Client() - objectTypes, err := client.BatchUpdateObjectTypes(context.Background(), test.options) + resourceTypes, err := client.BatchUpdateResourceTypes(context.Background(), test.options) if test.err { require.Error(t, err) return } require.NoError(t, err) - require.Equal(t, test.expected, objectTypes) + require.Equal(t, test.expected, resourceTypes) }) } } -func batchUpdateObjectTypesTestHandler(w http.ResponseWriter, r *http.Request) { +func batchUpdateResourceTypesTestHandler(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if auth != "Bearer test" { http.Error(w, "bad auth", http.StatusUnauthorized) @@ -392,7 +392,7 @@ func batchUpdateObjectTypesTestHandler(w http.ResponseWriter, r *http.Request) { return } - body, err := json.Marshal([]ObjectType{ + body, err := json.Marshal([]ResourceType{ { Type: "report", Relations: map[string]interface{}{ @@ -419,12 +419,12 @@ func batchUpdateObjectTypesTestHandler(w http.ResponseWriter, r *http.Request) { w.Write(body) } -func TestCreateObject(t *testing.T) { +func TestCreateResource(t *testing.T) { tests := []struct { scenario string client *Client - options CreateObjectOpts - expected Object + options CreateResourceOpts + expected Resource err bool }{ { @@ -433,98 +433,98 @@ func TestCreateObject(t *testing.T) { err: true, }, { - scenario: "Request returns Object", + scenario: "Request returns Resource", client: &Client{ APIKey: "test", }, - options: CreateObjectOpts{ - ObjectType: "report", - ObjectId: "sso_1710", + options: CreateResourceOpts{ + ResourceType: "report", + ResourceId: "sso_1710", }, - expected: Object{ - ObjectType: "report", - ObjectId: "sso_1710", + expected: Resource{ + ResourceType: "report", + ResourceId: "sso_1710", }, }, { - scenario: "Request returns Object with Metadata", + scenario: "Request returns Resource with Metadata", client: &Client{ APIKey: "test", }, - options: CreateObjectOpts{ - ObjectType: "report", - ObjectId: "sso_1710", + options: CreateResourceOpts{ + ResourceType: "report", + ResourceId: "sso_1710", Meta: map[string]interface{}{ "description": "Some report", }, }, - expected: Object{ - ObjectType: "report", - ObjectId: "sso_1710", + expected: Resource{ + ResourceType: "report", + ResourceId: "sso_1710", Meta: map[string]interface{}{ "description": "Some report", }, }, }, { - scenario: "Request with no ObjectId returns an Object with generated report", + scenario: "Request with no ResourceId returns an Resource with generated report", client: &Client{ APIKey: "test", }, - options: CreateObjectOpts{ - ObjectType: "report", + options: CreateResourceOpts{ + ResourceType: "report", }, - expected: Object{ - ObjectType: "report", - ObjectId: "report_1029384756", + expected: Resource{ + ResourceType: "report", + ResourceId: "report_1029384756", }, }, } for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(createObjectTestHandler)) + server := httptest.NewServer(http.HandlerFunc(createResourceTestHandler)) defer server.Close() client := test.client client.Endpoint = server.URL client.HTTPClient = server.Client() - object, err := client.CreateObject(context.Background(), test.options) + resource, err := client.CreateResource(context.Background(), test.options) if test.err { require.Error(t, err) return } require.NoError(t, err) - require.Equal(t, test.expected, object) + require.Equal(t, test.expected, resource) }) } } -func createObjectTestHandler(w http.ResponseWriter, r *http.Request) { +func createResourceTestHandler(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if auth != "Bearer test" { http.Error(w, "bad auth", http.StatusUnauthorized) return } - var opts CreateObjectOpts + var opts CreateResourceOpts json.NewDecoder(r.Body).Decode(&opts) if userAgent := r.Header.Get("User-Agent"); !strings.Contains(userAgent, "workos-go/") { w.WriteHeader(http.StatusBadRequest) return } - objectId := "sso_1710" - if opts.ObjectId == "" { - objectId = "report_1029384756" + resourceId := "sso_1710" + if opts.ResourceId == "" { + resourceId = "report_1029384756" } body, err := json.Marshal( - Object{ - ObjectType: "report", - ObjectId: objectId, - Meta: opts.Meta, + Resource{ + ResourceType: "report", + ResourceId: resourceId, + Meta: opts.Meta, }) if err != nil { @@ -536,12 +536,12 @@ func createObjectTestHandler(w http.ResponseWriter, r *http.Request) { w.Write(body) } -func TestUpdateObject(t *testing.T) { +func TestUpdateResource(t *testing.T) { tests := []struct { scenario string client *Client - options UpdateObjectOpts - expected Object + options UpdateResourceOpts + expected Resource err bool }{ { @@ -550,20 +550,20 @@ func TestUpdateObject(t *testing.T) { err: true, }, { - scenario: "Request returns Object with updated Meta", + scenario: "Request returns Resource with updated Meta", client: &Client{ APIKey: "test", }, - options: UpdateObjectOpts{ - ObjectType: "report", - ObjectId: "lad_8812", + options: UpdateResourceOpts{ + ResourceType: "report", + ResourceId: "lad_8812", Meta: map[string]interface{}{ "description": "Updated report", }, }, - expected: Object{ - ObjectType: "report", - ObjectId: "lad_8812", + expected: Resource{ + ResourceType: "report", + ResourceId: "lad_8812", Meta: map[string]interface{}{ "description": "Updated report", }, @@ -573,25 +573,25 @@ func TestUpdateObject(t *testing.T) { for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(updateObjectTestHandler)) + server := httptest.NewServer(http.HandlerFunc(updateResourceTestHandler)) defer server.Close() client := test.client client.Endpoint = server.URL client.HTTPClient = server.Client() - object, err := client.UpdateObject(context.Background(), test.options) + resource, err := client.UpdateResource(context.Background(), test.options) if test.err { require.Error(t, err) return } require.NoError(t, err) - require.Equal(t, test.expected, object) + require.Equal(t, test.expected, resource) }) } } -func updateObjectTestHandler(w http.ResponseWriter, r *http.Request) { +func updateResourceTestHandler(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if auth != "Bearer test" { http.Error(w, "bad auth", http.StatusUnauthorized) @@ -604,9 +604,9 @@ func updateObjectTestHandler(w http.ResponseWriter, r *http.Request) { } body, err := json.Marshal( - Object{ - ObjectType: "report", - ObjectId: "lad_8812", + Resource{ + ResourceType: "report", + ResourceId: "lad_8812", Meta: map[string]interface{}{ "description": "Updated report", }, @@ -621,11 +621,11 @@ func updateObjectTestHandler(w http.ResponseWriter, r *http.Request) { w.Write(body) } -func TestDeleteObject(t *testing.T) { +func TestDeleteResource(t *testing.T) { tests := []struct { scenario string client *Client - options DeleteObjectOpts + options DeleteResourceOpts expected error err bool }{ @@ -635,39 +635,39 @@ func TestDeleteObject(t *testing.T) { err: true, }, { - scenario: "Request returns Object", + scenario: "Request returns Resource", client: &Client{ APIKey: "test", }, - options: DeleteObjectOpts{ - ObjectType: "user", - ObjectId: "user_01SXW182", + options: DeleteResourceOpts{ + ResourceType: "user", + ResourceId: "user_01SXW182", }, expected: nil, }, { - scenario: "Request for non-existent Object returns error", + scenario: "Request for non-existent Resource returns error", client: &Client{ APIKey: "test", }, err: true, - options: DeleteObjectOpts{ - ObjectType: "user", - ObjectId: "safgdfgs", + options: DeleteResourceOpts{ + ResourceType: "user", + ResourceId: "safgdfgs", }, }, } for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(deleteObjectTestHandler)) + server := httptest.NewServer(http.HandlerFunc(deleteResourceTestHandler)) defer server.Close() client := test.client client.Endpoint = server.URL client.HTTPClient = server.Client() - err := client.DeleteObject(context.Background(), test.options) + err := client.DeleteResource(context.Background(), test.options) if test.err { require.Error(t, err) return @@ -678,23 +678,23 @@ func TestDeleteObject(t *testing.T) { } } -func deleteObjectTestHandler(w http.ResponseWriter, r *http.Request) { +func deleteResourceTestHandler(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if auth != "Bearer test" { http.Error(w, "bad auth", http.StatusUnauthorized) return } - var opts CreateObjectOpts + var opts CreateResourceOpts json.NewDecoder(r.Body).Decode(&opts) var body []byte var err error - if r.URL.Path == "/fga/v1/objects/user/user_01SXW182" { + if r.URL.Path == "/fga/v1/resources/user/user_01SXW182" { body, err = nil, nil } else { - http.Error(w, fmt.Sprintf("%s %s not found", opts.ObjectType, opts.ObjectId), http.StatusNotFound) + http.Error(w, fmt.Sprintf("%s %s not found", opts.ResourceType, opts.ResourceId), http.StatusNotFound) return } @@ -726,27 +726,27 @@ func TestListWarrants(t *testing.T) { APIKey: "test", }, options: ListWarrantsOpts{ - ObjectType: "report", + ResourceType: "report", }, expected: ListWarrantsResponse{ Data: []Warrant{ { - ObjectType: "report", - ObjectId: "ljc_1029", - Relation: "member", + ResourceType: "report", + ResourceId: "ljc_1029", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, { - ObjectType: "report", - ObjectId: "aut_7403", - Relation: "member", + ResourceType: "report", + ResourceId: "aut_7403", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, @@ -767,13 +767,13 @@ func TestListWarrants(t *testing.T) { client.Endpoint = server.URL client.HTTPClient = server.Client() - objects, err := client.ListWarrants(context.Background(), test.options) + resources, err := client.ListWarrants(context.Background(), test.options) if test.err { require.Error(t, err) return } require.NoError(t, err) - require.Equal(t, test.expected, objects) + require.Equal(t, test.expected, resources) }) } } @@ -796,21 +796,21 @@ func listWarrantsTestHandler(w http.ResponseWriter, r *http.Request) { ListWarrantsResponse: ListWarrantsResponse{ Data: []Warrant{ { - ObjectType: "report", - ObjectId: "ljc_1029", - Relation: "member", + ResourceType: "report", + ResourceId: "ljc_1029", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, { - ObjectType: "report", - ObjectId: "aut_7403", - Relation: "member", + ResourceType: "report", + ResourceId: "aut_7403", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, @@ -848,12 +848,12 @@ func TestWriteWarrant(t *testing.T) { APIKey: "test", }, options: WriteWarrantOpts{ - ObjectType: "report", - ObjectId: "sso_1710", - Relation: "member", + ResourceType: "report", + ResourceId: "sso_1710", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, expected: WriteWarrantResponse{ @@ -866,13 +866,13 @@ func TestWriteWarrant(t *testing.T) { APIKey: "test", }, options: WriteWarrantOpts{ - Op: "create", - ObjectType: "report", - ObjectId: "sso_1710", - Relation: "member", + Op: "create", + ResourceType: "report", + ResourceId: "sso_1710", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, expected: WriteWarrantResponse{ @@ -885,13 +885,13 @@ func TestWriteWarrant(t *testing.T) { APIKey: "test", }, options: WriteWarrantOpts{ - Op: "delete", - ObjectType: "report", - ObjectId: "sso_1710", - Relation: "member", + Op: "delete", + ResourceType: "report", + ResourceId: "sso_1710", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, expected: WriteWarrantResponse{ @@ -940,23 +940,23 @@ func TestBatchWriteWarrants(t *testing.T) { }, options: []WriteWarrantOpts{ { - Op: "delete", - ObjectType: "report", - ObjectId: "sso_1710", - Relation: "viewer", + Op: "delete", + ResourceType: "report", + ResourceId: "sso_1710", + Relation: "viewer", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, { - Op: "create", - ObjectType: "report", - ObjectId: "sso_1710", - Relation: "editor", + Op: "create", + ResourceType: "report", + ResourceId: "sso_1710", + Relation: "editor", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, @@ -1033,12 +1033,12 @@ func TestCheck(t *testing.T) { options: CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: "report", - ObjectId: "ljc_1029", - Relation: "member", + ResourceType: "report", + ResourceId: "ljc_1029", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, @@ -1120,21 +1120,21 @@ func TestCheckBatch(t *testing.T) { options: CheckBatchOpts{ Checks: []WarrantCheck{ { - ObjectType: "report", - ObjectId: "ljc_1029", - Relation: "member", + ResourceType: "report", + ResourceId: "ljc_1029", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, { - ObjectType: "report", - ObjectId: "spt_8521", - Relation: "member", + ResourceType: "report", + ResourceId: "spt_8521", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, @@ -1233,16 +1233,16 @@ func TestQuery(t *testing.T) { expected: QueryResponse{ Data: []QueryResult{ { - ObjectType: "role", - ObjectId: "role_01SXW182", - Relation: "member", + ResourceType: "role", + ResourceId: "role_01SXW182", + Relation: "member", Warrant: Warrant{ - ObjectType: "role", - ObjectId: "role_01SXW182", - Relation: "member", + ResourceType: "role", + ResourceId: "role_01SXW182", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, @@ -1293,16 +1293,16 @@ func queryTestHandler(w http.ResponseWriter, r *http.Request) { QueryResponse: QueryResponse{ Data: []QueryResult{ { - ObjectType: "role", - ObjectId: "role_01SXW182", - Relation: "member", + ResourceType: "role", + ResourceId: "role_01SXW182", + Relation: "member", Warrant: Warrant{ - ObjectType: "role", - ObjectId: "role_01SXW182", - Relation: "member", + ResourceType: "role", + ResourceId: "role_01SXW182", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, diff --git a/pkg/fga/fga.go b/pkg/fga/fga.go index 0ad3ddb2..70f226dd 100644 --- a/pkg/fga/fga.go +++ b/pkg/fga/fga.go @@ -14,60 +14,60 @@ func SetAPIKey(apiKey string) { DefaultClient.APIKey = apiKey } -// GetObject gets an Object. -func GetObject( +// GetResource gets a Resource. +func GetResource( ctx context.Context, - opts GetObjectOpts, -) (Object, error) { - return DefaultClient.GetObject(ctx, opts) + opts GetResourceOpts, +) (Resource, error) { + return DefaultClient.GetResource(ctx, opts) } -// ListObjects gets a list of Objects. -func ListObjects( +// ListResources gets a list of Resources. +func ListResources( ctx context.Context, - opts ListObjectsOpts, -) (ListObjectsResponse, error) { - return DefaultClient.ListObjects(ctx, opts) + opts ListResourcesOpts, +) (ListResourcesResponse, error) { + return DefaultClient.ListResources(ctx, opts) } -// CreateObject creates an Object. -func CreateObject( +// CreateResource creates a Resource. +func CreateResource( ctx context.Context, - opts CreateObjectOpts, -) (Object, error) { - return DefaultClient.CreateObject(ctx, opts) + opts CreateResourceOpts, +) (Resource, error) { + return DefaultClient.CreateResource(ctx, opts) } -// UpdateObject updates an Object. -func UpdateObject( +// UpdateResource updates a Resource. +func UpdateResource( ctx context.Context, - opts UpdateObjectOpts, -) (Object, error) { - return DefaultClient.UpdateObject(ctx, opts) + opts UpdateResourceOpts, +) (Resource, error) { + return DefaultClient.UpdateResource(ctx, opts) } -// DeleteObject deletes an Object. -func DeleteObject( +// DeleteResource deletes a Resource. +func DeleteResource( ctx context.Context, - opts DeleteObjectOpts, + opts DeleteResourceOpts, ) error { - return DefaultClient.DeleteObject(ctx, opts) + return DefaultClient.DeleteResource(ctx, opts) } -// ListObjectTypes gets a list of ObjectTypes. -func ListObjectTypes( +// ListResourceTypes gets a list of ResourceTypes. +func ListResourceTypes( ctx context.Context, - opts ListObjectTypesOpts, -) (ListObjectTypesResponse, error) { - return DefaultClient.ListObjectTypes(ctx, opts) + opts ListResourceTypesOpts, +) (ListResourceTypesResponse, error) { + return DefaultClient.ListResourceTypes(ctx, opts) } -// BatchUpdateObjectTypes sets the environment's object types to match the provided types. -func BatchUpdateObjectTypes( +// BatchUpdateResourceTypes sets the environment's object types to match the provided types. +func BatchUpdateResourceTypes( ctx context.Context, - opts []UpdateObjectTypeOpts, -) ([]ObjectType, error) { - return DefaultClient.BatchUpdateObjectTypes(ctx, opts) + opts []UpdateResourceTypeOpts, +) ([]ResourceType, error) { + return DefaultClient.BatchUpdateResourceTypes(ctx, opts) } // ListWarrants gets a list of Warrants. diff --git a/pkg/fga/fga_test.go b/pkg/fga/fga_test.go index b1ff62a3..3ef0ef6b 100644 --- a/pkg/fga/fga_test.go +++ b/pkg/fga/fga_test.go @@ -10,8 +10,8 @@ import ( "github.com/workos/workos-go/v4/pkg/common" ) -func TestFGAGetObject(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(getObjectTestHandler)) +func TestFGAGetResource(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(getResourceTestHandler)) defer server.Close() DefaultClient = &Client{ @@ -20,21 +20,21 @@ func TestFGAGetObject(t *testing.T) { } SetAPIKey("test") - expectedResponse := Object{ - ObjectType: "report", - ObjectId: "ljc_1029", + expectedResponse := Resource{ + ResourceType: "report", + ResourceId: "ljc_1029", } - objectResponse, err := GetObject(context.Background(), GetObjectOpts{ - ObjectType: "report", - ObjectId: "ljc_1029", + resourceResponse, err := GetResource(context.Background(), GetResourceOpts{ + ResourceType: "report", + ResourceId: "ljc_1029", }) require.NoError(t, err) - require.Equal(t, expectedResponse, objectResponse) + require.Equal(t, expectedResponse, resourceResponse) } -func TestFGAListObjects(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(listObjectsTestHandler)) +func TestFGAListResources(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(listResourcesTestHandler)) defer server.Close() DefaultClient = &Client{ @@ -43,15 +43,15 @@ func TestFGAListObjects(t *testing.T) { } SetAPIKey("test") - expectedResponse := ListObjectsResponse{ - Data: []Object{ + expectedResponse := ListResourcesResponse{ + Data: []Resource{ { - ObjectType: "report", - ObjectId: "ljc_1029", + ResourceType: "report", + ResourceId: "ljc_1029", }, { - ObjectType: "report", - ObjectId: "mso_0806", + ResourceType: "report", + ResourceId: "mso_0806", }, }, ListMetadata: common.ListMetadata{ @@ -59,16 +59,16 @@ func TestFGAListObjects(t *testing.T) { After: "", }, } - objectsResponse, err := ListObjects(context.Background(), ListObjectsOpts{ - ObjectType: "report", + resourcesResponse, err := ListResources(context.Background(), ListResourcesOpts{ + ResourceType: "report", }) require.NoError(t, err) - require.Equal(t, expectedResponse, objectsResponse) + require.Equal(t, expectedResponse, resourcesResponse) } -func TestFGACreateObject(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(createObjectTestHandler)) +func TestFGACreateResource(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(createResourceTestHandler)) defer server.Close() DefaultClient = &Client{ @@ -77,21 +77,21 @@ func TestFGACreateObject(t *testing.T) { } SetAPIKey("test") - expectedResponse := Object{ - ObjectType: "report", - ObjectId: "sso_1710", + expectedResponse := Resource{ + ResourceType: "report", + ResourceId: "sso_1710", } - createdObject, err := CreateObject(context.Background(), CreateObjectOpts{ - ObjectType: "report", - ObjectId: "sso_1710", + createdResource, err := CreateResource(context.Background(), CreateResourceOpts{ + ResourceType: "report", + ResourceId: "sso_1710", }) require.NoError(t, err) - require.Equal(t, expectedResponse, createdObject) + require.Equal(t, expectedResponse, createdResource) } -func TestFGAUpdateObject(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(updateObjectTestHandler)) +func TestFGAUpdateResource(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(updateResourceTestHandler)) defer server.Close() DefaultClient = &Client{ @@ -100,27 +100,27 @@ func TestFGAUpdateObject(t *testing.T) { } SetAPIKey("test") - expectedResponse := Object{ - ObjectType: "report", - ObjectId: "lad_8812", + expectedResponse := Resource{ + ResourceType: "report", + ResourceId: "lad_8812", Meta: map[string]interface{}{ "description": "Updated report", }, } - updatedObject, err := UpdateObject(context.Background(), UpdateObjectOpts{ - ObjectType: "report", - ObjectId: "lad_8812", + updatedResource, err := UpdateResource(context.Background(), UpdateResourceOpts{ + ResourceType: "report", + ResourceId: "lad_8812", Meta: map[string]interface{}{ "description": "Updated report", }, }) require.NoError(t, err) - require.Equal(t, expectedResponse, updatedObject) + require.Equal(t, expectedResponse, updatedResource) } -func TestFGADeleteObject(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(deleteObjectTestHandler)) +func TestFGADeleteResource(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(deleteResourceTestHandler)) defer server.Close() DefaultClient = &Client{ @@ -129,16 +129,16 @@ func TestFGADeleteObject(t *testing.T) { } SetAPIKey("test") - err := DeleteObject(context.Background(), DeleteObjectOpts{ - ObjectType: "user", - ObjectId: "user_01SXW182", + err := DeleteResource(context.Background(), DeleteResourceOpts{ + ResourceType: "user", + ResourceId: "user_01SXW182", }) require.NoError(t, err) } -func TestFGAListObjectTypes(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(listObjectTypesTestHandler)) +func TestFGAListResourceTypes(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(listResourceTypesTestHandler)) defer server.Close() DefaultClient = &Client{ @@ -147,8 +147,8 @@ func TestFGAListObjectTypes(t *testing.T) { } SetAPIKey("test") - expectedResponse := ListObjectTypesResponse{ - Data: []ObjectType{ + expectedResponse := ListResourceTypesResponse{ + Data: []ResourceType{ { Type: "report", Relations: map[string]interface{}{ @@ -171,16 +171,16 @@ func TestFGAListObjectTypes(t *testing.T) { After: "", }, } - objectTypesResponse, err := ListObjectTypes(context.Background(), ListObjectTypesOpts{ + resourceTypesResponse, err := ListResourceTypes(context.Background(), ListResourceTypesOpts{ Order: "asc", }) require.NoError(t, err) - require.Equal(t, expectedResponse, objectTypesResponse) + require.Equal(t, expectedResponse, resourceTypesResponse) } -func TestFGABatchUpdateObjectTypes(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(batchUpdateObjectTypesTestHandler)) +func TestFGABatchUpdateResourceTypes(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(batchUpdateResourceTypesTestHandler)) defer server.Close() DefaultClient = &Client{ @@ -189,7 +189,7 @@ func TestFGABatchUpdateObjectTypes(t *testing.T) { } SetAPIKey("test") - expectedResponse := []ObjectType{ + expectedResponse := []ResourceType{ { Type: "report", Relations: map[string]interface{}{ @@ -207,7 +207,7 @@ func TestFGABatchUpdateObjectTypes(t *testing.T) { Relations: map[string]interface{}{}, }, } - objectTypes, err := BatchUpdateObjectTypes(context.Background(), []UpdateObjectTypeOpts{ + resourceTypes, err := BatchUpdateResourceTypes(context.Background(), []UpdateResourceTypeOpts{ { Type: "report", Relations: map[string]interface{}{ @@ -227,7 +227,7 @@ func TestFGABatchUpdateObjectTypes(t *testing.T) { }) require.NoError(t, err) - require.Equal(t, expectedResponse, objectTypes) + require.Equal(t, expectedResponse, resourceTypes) } func TestFGAListWarrants(t *testing.T) { @@ -243,21 +243,21 @@ func TestFGAListWarrants(t *testing.T) { expectedResponse := ListWarrantsResponse{ Data: []Warrant{ { - ObjectType: "report", - ObjectId: "ljc_1029", - Relation: "member", + ResourceType: "report", + ResourceId: "ljc_1029", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, { - ObjectType: "report", - ObjectId: "aut_7403", - Relation: "member", + ResourceType: "report", + ResourceId: "aut_7403", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, @@ -267,7 +267,7 @@ func TestFGAListWarrants(t *testing.T) { }, } warrantsResponse, err := ListWarrants(context.Background(), ListWarrantsOpts{ - ObjectType: "report", + ResourceType: "report", }) require.NoError(t, err) @@ -288,13 +288,13 @@ func TestFGAWriteWarrant(t *testing.T) { WarrantToken: "new_warrant_token", } warrantResponse, err := WriteWarrant(context.Background(), WriteWarrantOpts{ - Op: "create", - ObjectType: "report", - ObjectId: "sso_1710", - Relation: "member", + Op: "create", + ResourceType: "report", + ResourceId: "sso_1710", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }) @@ -317,23 +317,23 @@ func TestFGABatchWriteWarrants(t *testing.T) { } warrantResponse, err := BatchWriteWarrants(context.Background(), []WriteWarrantOpts{ { - Op: "delete", - ObjectType: "report", - ObjectId: "sso_1710", - Relation: "viewer", + Op: "delete", + ResourceType: "report", + ResourceId: "sso_1710", + Relation: "viewer", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, { - Op: "create", - ObjectType: "report", - ObjectId: "sso_1710", - Relation: "editor", + Op: "create", + ResourceType: "report", + ResourceId: "sso_1710", + Relation: "editor", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }) @@ -355,12 +355,12 @@ func TestFGACheck(t *testing.T) { checkResponse, err := Check(context.Background(), CheckOpts{ Checks: []WarrantCheck{ { - ObjectType: "report", - ObjectId: "ljc_1029", - Relation: "member", + ResourceType: "report", + ResourceId: "ljc_1029", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, @@ -383,12 +383,12 @@ func TestFGACheckBatch(t *testing.T) { checkResponses, err := CheckBatch(context.Background(), CheckBatchOpts{ Checks: []WarrantCheck{ { - ObjectType: "report", - ObjectId: "ljc_1029", - Relation: "member", + ResourceType: "report", + ResourceId: "ljc_1029", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, @@ -413,16 +413,16 @@ func TestFGAQuery(t *testing.T) { expectedResponse := QueryResponse{ Data: []QueryResult{ { - ObjectType: "role", - ObjectId: "role_01SXW182", - Relation: "member", + ResourceType: "role", + ResourceId: "role_01SXW182", + Relation: "member", Warrant: Warrant{ - ObjectType: "role", - ObjectId: "role_01SXW182", - Relation: "member", + ResourceType: "role", + ResourceId: "role_01SXW182", + Relation: "member", Subject: Subject{ - ObjectType: "user", - ObjectId: "user_01SXW182", + ResourceType: "user", + ResourceId: "user_01SXW182", }, }, }, From fd93f4edbac76210087c5a8ee0d433cbeab6bc16 Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Fri, 19 Jul 2024 11:01:52 -0700 Subject: [PATCH 13/17] Update json encoding for checks --- pkg/fga/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index 879c424f..79746958 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -306,7 +306,7 @@ type CheckOpts struct { type CheckBatchOpts struct { // List of warrants to check. - Checks []WarrantCheck `json:"warrants"` + Checks []WarrantCheck `json:"checks"` // Flag to include debug information in the response. Debug bool `json:"debug,omitempty"` From 4b7c1b179bff79dcf04ba1319922991a5e1d4a1d Mon Sep 17 00:00:00 2001 From: Karan Kajla Date: Mon, 22 Jul 2024 09:20:07 -0700 Subject: [PATCH 14/17] Update CheckResultAuthorized constant to be lowercase and add new CheckResultNotAuthorized constant --- pkg/fga/client.go | 8 ++++---- pkg/fga/client_test.go | 18 ++++++------------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index 79746958..1327e304 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -23,9 +23,10 @@ type Order string // Constants that enumerate the available orders. const ( - CheckResultAuthorized = "Authorized" - Asc Order = "asc" - Desc Order = "desc" + CheckResultAuthorized = "authorized" + CheckResultNotAuthorized = "not_authorized" + Asc Order = "asc" + Desc Order = "desc" ) // Client represents a client that performs FGA requests to the WorkOS API. @@ -316,7 +317,6 @@ type CheckBatchOpts struct { } type CheckResponse struct { - Code int64 `json:"code"` Result string `json:"result"` IsImplicit bool `json:"is_implicit"` DebugInfo DebugInfo `json:"debug_info,omitempty"` diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go index 4a165078..9943126e 100644 --- a/pkg/fga/client_test.go +++ b/pkg/fga/client_test.go @@ -1044,8 +1044,7 @@ func TestCheck(t *testing.T) { }, }, expected: CheckResponse{ - Code: 200, - Result: "Authorized", + Result: CheckResultAuthorized, IsImplicit: false, }, }, @@ -1085,8 +1084,7 @@ func checkTestHandler(w http.ResponseWriter, r *http.Request) { body, err := json.Marshal( CheckResponse{ - Code: 200, - Result: "Authorized", + Result: CheckResultAuthorized, IsImplicit: false, }) @@ -1141,13 +1139,11 @@ func TestCheckBatch(t *testing.T) { }, expected: []CheckResponse{ { - Code: 200, - Result: "Authorized", + Result: CheckResultAuthorized, IsImplicit: false, }, { - Code: 403, - Result: "Not Authorized", + Result: CheckResultNotAuthorized, IsImplicit: false, }, }, @@ -1189,13 +1185,11 @@ func checkBatchTestHandler(w http.ResponseWriter, r *http.Request) { body, err := json.Marshal( []CheckResponse{ { - Code: 200, - Result: "Authorized", + Result: CheckResultAuthorized, IsImplicit: false, }, { - Code: 403, - Result: "Not Authorized", + Result: CheckResultNotAuthorized, IsImplicit: false, }, }) From 02d52004b16b39b9f9d7dcc09521e844577f65d1 Mon Sep 17 00:00:00 2001 From: Karan Kajla Date: Mon, 22 Jul 2024 12:12:46 -0700 Subject: [PATCH 15/17] Add constants for Warrant create/delete operations + add constants for check operations ('any_of', 'all_of', etc). --- pkg/fga/client.go | 11 ++++++++--- pkg/fga/client_test.go | 8 ++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index 1327e304..edd1dc44 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -23,10 +23,15 @@ type Order string // Constants that enumerate the available orders. const ( - CheckResultAuthorized = "authorized" - CheckResultNotAuthorized = "not_authorized" Asc Order = "asc" Desc Order = "desc" + CheckOpAllOf = "all_of" + CheckOpAnyOf = "any_of" + CheckOpBatch = "batch" + CheckResultAuthorized = "authorized" + CheckResultNotAuthorized = "not_authorized" + WarrantOpCreate = "create" + WarrantOpDelete = "delete" ) // Client represents a client that performs FGA requests to the WorkOS API. @@ -817,7 +822,7 @@ func (c *Client) CheckBatch(ctx context.Context, opts CheckBatchOpts) ([]CheckRe c.once.Do(c.init) checkOpts := CheckOpts{ - Op: "batch", + Op: CheckOpBatch, Checks: opts.Checks, Debug: opts.Debug, WarrantToken: opts.WarrantToken, diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go index 9943126e..d984200d 100644 --- a/pkg/fga/client_test.go +++ b/pkg/fga/client_test.go @@ -866,7 +866,7 @@ func TestWriteWarrant(t *testing.T) { APIKey: "test", }, options: WriteWarrantOpts{ - Op: "create", + Op: WarrantOpCreate, ResourceType: "report", ResourceId: "sso_1710", Relation: "member", @@ -885,7 +885,7 @@ func TestWriteWarrant(t *testing.T) { APIKey: "test", }, options: WriteWarrantOpts{ - Op: "delete", + Op: WarrantOpDelete, ResourceType: "report", ResourceId: "sso_1710", Relation: "member", @@ -940,7 +940,7 @@ func TestBatchWriteWarrants(t *testing.T) { }, options: []WriteWarrantOpts{ { - Op: "delete", + Op: WarrantOpDelete, ResourceType: "report", ResourceId: "sso_1710", Relation: "viewer", @@ -950,7 +950,7 @@ func TestBatchWriteWarrants(t *testing.T) { }, }, { - Op: "create", + Op: WarrantOpCreate, ResourceType: "report", ResourceId: "sso_1710", Relation: "editor", From 0528aedbab69a35b9f56bd7fb78c10abeab66392 Mon Sep 17 00:00:00 2001 From: Karan Kajla Date: Tue, 23 Jul 2024 09:17:04 -0700 Subject: [PATCH 16/17] Fix grammar in resource comments --- pkg/fga/client.go | 10 +++++----- pkg/fga/client_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index edd1dc44..fbfe83c8 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -90,7 +90,7 @@ type ListResourcesOpts struct { // The type of the resource. ResourceType string `url:"resource_type,omitempty"` - // Searchable text for an Resource. Can be empty. + // Searchable text for a Resource. Can be empty. Search string `url:"search,omitempty"` // Maximum number of records to return. @@ -137,7 +137,7 @@ type UpdateResourceOpts struct { Meta map[string]interface{} `json:"meta,omitempty"` } -// DeleteResourceOpts contains the options to delete an resource. +// DeleteResourceOpts contains the options to delete a resource. type DeleteResourceOpts struct { // The type of the resource. ResourceType string @@ -396,7 +396,7 @@ type QueryResponse struct { ListMetadata common.ListMetadata `json:"list_metadata"` } -// GetResource gets an Resource. +// GetResource gets a Resource. func (c *Client) GetResource(ctx context.Context, opts GetResourceOpts) (Resource, error) { c.once.Do(c.init) @@ -513,7 +513,7 @@ func (c *Client) CreateResource(ctx context.Context, opts CreateResourceOpts) (R func (c *Client) UpdateResource(ctx context.Context, opts UpdateResourceOpts) (Resource, error) { c.once.Do(c.init) - // UpdateResourceChangeOpts contains the options to update an Resource minus the ResourceType and ResourceId + // UpdateResourceChangeOpts contains the options to update a Resource minus the ResourceType and ResourceId type UpdateResourceChangeOpts struct { Meta map[string]interface{} `json:"meta"` } @@ -553,7 +553,7 @@ func (c *Client) UpdateResource(ctx context.Context, opts UpdateResourceOpts) (R } -// DeleteResource deletes an Resource +// DeleteResource deletes a Resource func (c *Client) DeleteResource(ctx context.Context, opts DeleteResourceOpts) error { c.once.Do(c.init) diff --git a/pkg/fga/client_test.go b/pkg/fga/client_test.go index d984200d..c3713aef 100644 --- a/pkg/fga/client_test.go +++ b/pkg/fga/client_test.go @@ -27,7 +27,7 @@ func TestGetResource(t *testing.T) { err: true, }, { - scenario: "Request returns an Resource", + scenario: "Request returns a Resource", client: &Client{ APIKey: "test", }, @@ -467,7 +467,7 @@ func TestCreateResource(t *testing.T) { }, }, { - scenario: "Request with no ResourceId returns an Resource with generated report", + scenario: "Request with no ResourceId returns a Resource with generated report id", client: &Client{ APIKey: "test", }, From afd73ceeb03ac288a6a00180b111699b18cf2b50 Mon Sep 17 00:00:00 2001 From: Stanley Phu Date: Tue, 23 Jul 2024 10:12:10 -0700 Subject: [PATCH 17/17] Implement EncodeValues for Context type --- pkg/fga/client.go | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/pkg/fga/client.go b/pkg/fga/client.go index fbfe83c8..8fe92442 100644 --- a/pkg/fga/client.go +++ b/pkg/fga/client.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "sync" "time" @@ -279,6 +280,15 @@ type WriteWarrantResponse struct { // Check type Context map[string]interface{} +func (context Context) EncodeValues(key string, values *url.Values) error { + jsonCtx, err := json.Marshal(context) + if err != nil { + return err + } + values.Set(key, string(jsonCtx)) + return nil +} + type WarrantCheck struct { // The type of the resource. ResourceType string `json:"resource_type"` @@ -892,33 +902,7 @@ func (c *Client) Query(ctx context.Context, opts QueryOpts) (QueryResponse, erro opts.Order = Desc } - type QueryUrlOpts struct { - Query string `url:"q"` - Context string `url:"context,omitempty"` - Limit int `url:"limit,omitempty"` - Order Order `url:"order,omitempty"` - Before string `url:"before,omitempty"` - After string `url:"after,omitempty"` - WarrantToken string `url:"-"` - } - - var jsonCtx []byte - if opts.Context != nil { - jsonCtx, err = json.Marshal(opts.Context) - if err != nil { - return QueryResponse{}, err - } - } - queryUrlOpts := QueryUrlOpts{ - Query: opts.Query, - Context: string(jsonCtx), - Limit: opts.Limit, - Order: opts.Order, - Before: opts.Before, - After: opts.After, - } - - q, err := query.Values(queryUrlOpts) + q, err := query.Values(opts) if err != nil { return QueryResponse{}, err }