Code Monkey home page Code Monkey logo

go-sdk's Introduction

OpenFGA

Go Reference GitHub release (latest SemVer) Docker Pulls Codecov Go Report CII Best Practices Join our community Twitter FOSSA Status Artifact HUB OpenSSF Scorecard SLSA 3

A high-performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar.

OpenFGA is designed to make it easy for developers to model their application permissions and add and integrate fine-grained authorization into their applications.

It allows in-memory data storage for quick development, as well as pluggable database modules. It currently supports PostgreSQL 14 and MySQL 8.

It offers an HTTP API and a gRPC API. It has SDKs for Java, Node.js/JavaScript, GoLang, Python and .NET. Look in our Community section for third-party SDKs and tools. It can also be used as a library (see example).

Getting Started

The following section aims to help you get started quickly. Please look at our official documentation for in-depth information.

Setup and Installation

ℹī¸ The following sections setup an OpenFGA server using the default configuration values. These are for rapid development and not for a production environment. Data written to an OpenFGA instance using the default configuration with the memory storage engine will not persist after the service is stopped.

For more information on how to configure the OpenFGA server, please take a look at our official documentation on Running in Production.

Docker

OpenFGA is available on Dockerhub, so you can quickly start it using the in-memory datastore by running the following commands:

docker pull openfga/openfga
docker run -p 8080:8080 -p 3000:3000 openfga/openfga run

Tip

The OPENFGA_HTTP_ADDR environment variable can used to configure the address at which the playground expects the OpenFGA server to be. For example, docker run -e OPENFGA_PLAYGROUND_ENABLED=true -e OPENFGA_HTTP_ADDR=0.0.0.0:4000 -p 4000:4000 -p 3000:3000 openfga/openfga run will start the OpenFGA server on port 4000, and configure the playground too.

Docker Compose

docker-compose.yaml provides an example of how to launch OpenFGA with Postgres using docker compose.

  1. First, either clone this repo or curl the docker-compose.yaml file with the following command:

    curl -LO https://openfga.dev/docker-compose.yaml
  2. Then, run the following command:

    docker compose up

Package Managers

If you are a Homebrew user, you can install OpenFGA with the following command:

brew install openfga

Pre-compiled Binaries

Download your platform's latest release and extract it. Then run the binary with the command:

./openfga run

Building from Source

There are two recommended options for building OpenFGA from source code:

Building from source with go install

Make sure you have Go 1.20 or later installed. See the Go downloads page.

You can install from source using Go modules:

  1. First, make sure $GOBIN is on your shell $PATH:

    export PATH=$PATH:$(go env GOBIN)
  2. Then use the install command:

    go install github.com/openfga/openfga/cmd/openfga
  3. Run the server with:

    ./openfga run

Building from source with go build

Alternatively you can build OpenFGA by cloning the project from this Github repo, and then building it with the go build command:

  1. Clone the repo to a local directory, and navigate to that directory:

    git clone https://github.com/openfga/openfga.git && cd openfga
  2. Then use the build command:

    go build -o ./openfga ./cmd/openfga
  3. Run the server with:

    ./openfga run

Verifying the Installation

Now that you have Set up and Installed OpenFGA, you can test your installation by creating an OpenFGA Store.

curl -X POST 'localhost:8080/stores' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name": "openfga-demo"
}'

If everything is running correctly, you should get a response with information about the newly created store, for example:

{
  "id": "01G3EMTKQRKJ93PFVDA1SJHWD2",
  "name": "openfga-demo",
  "created_at": "2022-05-19T17:11:12.888680Z",
  "updated_at": "2022-05-19T17:11:12.888680Z"
}

Playground

The Playground facilitates rapid development by allowing you to visualize and model your application's authorization model(s) and manage relationship tuples with a locally running OpenFGA instance.

To run OpenFGA with the Playground disabled, provide the --playground-enabled=false flag.

./openfga run --playground-enabled=false

Once OpenFGA is running, by default, the Playground can be accessed at http://localhost:3000/playground.

In the event that a port other than the default port is required, the --playground-port flag can be set to change it. For example,

./openfga run --playground-enabled --playground-port 3001

Profiler (pprof)

Profiling through pprof can be enabled on the OpenFGA server by providing the --profiler-enabled flag.

./openfga run --profiler-enabled

This will start serving profiling data on port 3001. You can see that data by visiting http://localhost:3001/debug/pprof.

If you need to serve the profiler on a different address, you can do so by specifying the --profiler-addr flag. For example,

./openfga run --profiler-enabled --profiler-addr :3002

Once the OpenFGA server is running, in another window you can run the following command to generate a compressed CPU profile:

go tool pprof -proto -seconds 60 http://localhost:3001/debug/pprof/profile
# will collect data for 60 seconds and generate a file like pprof.samples.cpu.001.pb.gz

That file can be analyzed visually by running the following command and then visiting http://localhost:8084:

go tool pprof -http=localhost:8084 pprof.samples.cpu.001.pb.gz

Next Steps

Take a look at examples of how to:

Don't hesitate to browse the official Documentation, API Reference.

Limitations

MySQL Storage engine

The MySQL storage engine has a lower length limit for some properties of a tuple compared with other storage backends. For more information see the docs.

OpenFGA's MySQL Storage Adapter was contributed to OpenFGA by @twintag. Thanks!

Production Readiness

The core OpenFGA service has been in use by Okta FGA in production since December 2021.

OpenFGA's Memory Storage Adapter was built for development purposes only and is not recommended for a production environment, because it is not designed for scalable queries and has no support for persistence.

You can learn about more organizations using OpenFGA in production here. If your organization is using OpenFGA in production please consider adding it to the list.

The OpenFGA team will do its best to address all production issues with high priority.

Contributing

See CONTRIBUTING.

Community Meetings

We hold a monthly meeting to interact with the community, collaborate and receive/provide feedback. You can find more details, including the time, our agenda, and the meeting minutes here.

go-sdk's People

Contributors

aaguiarz avatar adriantam avatar balaji-dongare avatar bketelsen avatar booniepepper avatar dependabot[bot] avatar dguhr avatar evansims avatar ewanharris avatar gurleensethi avatar jimmyjames avatar jpadilla avatar le-yams avatar omegagussan avatar rhamzeh avatar willvedd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-sdk's Issues

Need to allow empty string for ulid

Description

Currently the Go-SDK will assert ulid is not well formed for empty string. Instead, it should allow.

Version of SDK

v0.2.0

Version of OpenFGA (if known)

v1.1.0

OpenFGA Flags/Custom Configuration Applicable

environment:
- OPENFGA_DATASTORE_ENGINE=postgres
- OPENFGA_DATASTORE_URI=postgres://postgres:password@postgres:5432/postgres?sslmode=disable
- OPENFGA_TRACE_ENABLED=true
- OPENFGA_TRACE_SAMPLE_RATIO=1
- OPENFGA_TRACE_OTLP_ENDPOINT=otel-collector:4317
- OPENFGA_METRICS_ENABLE_RPC_HISTOGRAMS=true

Reproduction

Detail the steps taken to reproduce this error, what was expected, and whether this issue can be reproduced consistently or if it is intermittent.

  1. Initialize OpenFgaClient with openfga_sdk.ClientConfiguration parameter api_host=127.0.0.1, credentials method client_credentials
  2. Invoke method read_authorization_models
  3. See exception thrown

Sample Code the Produces Issues

<code snippet>

Backtrace (if applicable)

<backtrace>

Expected behavior

A clear and concise description of what you expected to happen.

Additional context

Add any other context about the problem here.

oauth2 package retains canonical import path from original code

Description

oauth2 package retains canonical import path from original code, causing packages importing it to substitute the original golang.org/x/oauth2 package instead.

the credentials package in this repo imports oauth2/clientcredentials which has the canonical import path set to golang.org/x/oauth2.

Export metrics

Checklist

Describe the problem you'd like to have solved

As a consumer of the SDK, I would like to hook it to my dashboards to get data on several metrics.

Describe the ideal solution

Integrate the OpenTelemetry Go API library https://github.com/open-telemetry/opentelemetry-go

Expose only from the inner OpenFgaApi. We need to be able to get these metrics:

  • The latency of the request, split into
    • time it took since dev called the method until they get a response
    • time it took since the sdk issued a request to the API until it got a response
    • time reported by the server (query_duration_ms header)
  • Response codes
  • Error codes and method
  • Method calls

See here for examples on usage:

Go SDK Enhancement Opportunities

  • Reduce the stutter of client invocations
    The underlying OpenFgaApi client interface should be embedded. For example,

    config, err := openfga.NewConfiguration(openfga.Configuration{...})
    if err != nil {
    // .. Handle error
    }
    
    apiClient := openfga.NewAPIClient(config)
    
    // current design
    stores, response, err := apiClient.OpenFgaApi.ListStores(context.Background()).Execute()
    
    // recommended design
    stores, response, err := apiClient.ListStores(context.Background()).Execute()

    It's possible to just store the underlying OpenFgaApi as a variable and use that, but that also requires a level of indirection that is unecessary. Point is - the APIClient should have the API methods attached to it directly, and this is best achieved by embedding the underlying interface.

    type APIClient struct {
        ...
    	OpenFgaApi OpenFgaApi
    }

    changes to

    type APIClient struct {
        ...
        OpenFgaApi
    }
  • Make the API surface more idiomatic, streamlined, and RPC style

    The Go SDK is currently very verbose and not idiomatic. Building a Go integration with the OpenFGA Go SDK involved non-standard Go idioms, and this makes consuming the libraries API surface more challenging than other Go SDKs for other APIs.

    Our API is an RPC style API, and most Go clients that are idiomatic define client interfaces with request inputs and response outputs like so:

    type APIClientInterface interface {
        Read(ctx context.Context, req *ReadRequest) (*ReadResponse, error)
        Write(ctx context.Context, reqw *WriteRequest) (*WriteResponse, error)
    }

    In fact, if you look at the Go gRPC generated client stubs this is what generated code like this gets produced as. I acknowledge that our HTTP API defines custom mappings to our RPC API, and so we'd need to generate this structure off of the OpenAPI definition instead of inheriting the default HTTP-->gRPC transcoding mapping, but it would be nice to stay within this idiom.

    For example

    var client openfga.OpenFgaApi
    
    // current design
    readResp, httpResp, err := client.
      Read(context.Background()).
      Body(auth0fga.ReadRequest{...}).
      Execute() 
      
    // recommended design
    readResp, httpResp, err := client.Read(context.Background(), &auth0fga.ReadRequest{...})
  • Drop the HTTP response param from the return list
    Our code currently returns the raw HTTP response as part of the method signatures. I suppose this isn't necessarily a bad thing, but it's definitely a Go idiom I have not particularly seen that used in the wild. I would also argue that exposing the raw HTTP response is counter productive to the Go SDK in the first place. The SDK is supposed to be abstracting the underlying HTTP API, and this makes it possible for client applications to build integrations they depend on with the raw HTTP API. That makes this part of the API surface another thing we have to maintain compatibility with even though the SDK is purposefully trying to abstract it.

    I recommend we drop the HTTP response object from the response altogether and depend upon the error usage pattern to appropriately capture the error semantics of the underlying HTTP response behavior. Lots of Go API client libs define an APIError structure and it implements the error interface, and they return this with the status and any underlying error message (if status is non 200). Also, if you need access to the Headers (for rate limiting for example), then it's also a common Go idiom to embed those in the response object.

    var client openfga.OpenFgaApi
    
    // current design
    readResp, httpResp, err := client.Read(context.Background()).Body(...).Execute()
    
    if httpResp.StatusCode != http.StatusOK {
      // handle error behavior
    }
    
    if remaining := httpResp.Header.Get("x-ratelimit-remaining"); remaining > 1 {
        // do next request, for example
    }
    
    // recommended design
    type APIErrorStatus int // enum set of codes for consummable API errors
    
    type APIError struct {
        ... // other metadata as needed
        Status APIErrorStatus
        Message string
    }
    
    type APIResponse[T any] interface {
        GetResponse() (T, error)
        GetHeaders http.Header
    }
    
    readResp, err := client.Read(context.Background(), &openfga.ReadRequest{...})
    if err != nil {
        if errors.Is(err, openfga.APIError) {
            // handle specific `APIErrorStatus`
        }
    }
    
    if remaining := readResp.GetHeaders()["x-ratelimit-remaining"]; remaining > 1 {
        // do next request, for example
    }

    A great example of a widely used Go SDK that follows some common Go idioms and is very easy to consume is the Stripe Go SDK (https://github.com/stripe/stripe-go). There are examples of this ^^ in that SDK.

Client credentials flow does not support token issuer url with a path

Description

The OIDC authentication flow does not support token issuer urls containing a path, e.g. urls of Microsoft Entra ID.

Version of SDK

v0.3.0

Reproduction

fga client in version 0.2.1 (go-sdk v0.3.0) behaviour (replace and with your values):

$ fga store list --api-url http://localhost:8080 --client-id "$AZURE_CLIENT_ID" --client-secret "$AZURE_CLIENT_SECRET" --api-audience <api audience> --api-token-issuer "login.microsoftonline.com/<tenant id>"
Error: failed to initialize FGA Client due to Credentials are invalid: CredentialsConfig.ApiTokenIssuer (https://login.microsoftonline.com/<tenant id>) is in an invalid format

Expected behavior

API token URLs should support URLs with paths.

Additional context

The issue is caused by IsWellFormedUri function, which expects value to not contain path part.

Additionally, the value is used to build the final token URL value by adding a fixed /oauth/token path, which is not always a case - for example such URL in Microsoft Entra ID ends with either /oauth2/token or /oauth2/v2.0/token path.

Why the specific dependency on Go `1.21.9`?

Checklist

  • I have looked into the README and have not found a suitable solution or answer.
  • I have looked into the documentation and have not found a suitable solution or answer.
  • I have searched the issues and have not found a suitable solution or answer.
  • I have upgraded to the latest version of OpenFGA and the issue still persists.
  • I have searched the Slack community and have not found a suitable solution or answer.
  • I agree to the terms within the OpenFGA Code of Conduct.

Description

Hello,

I see that a recent change which then made it into 0.3.6 has caused a bump of Go from 1.21 to 1.21.9 but I can't figure out why.

This bump is preventing us from consuming 0.3.6 from within Incus as our base is still 1.21.

Expectation

Be able to build with Go 1.21.x

Reproduction

Build or consume go-sdk from a project using Go 1.21.x < 1.21.9.

OpenFGA SDK version

0.3.6

OpenFGA version

irrelevant

SDK Configuration

irrelevant

Logs

No response

References

No response

NewSdkClient should return SdkClient interface

Description

NewSdkClient should return satisfy interface. The SdkClient's ReadAuthorizationModel should return SdkClientReadAuthorizationModelRequestInterface.

Version of SDK

Pre-release v0.2.2

Version of OpenFGA (if known)

N/A

OpenFGA Flags/Custom Configuration Applicable

N/A

Reproduction

  1. Create a method where SdkClient interface is passed in as parameter
  2. Try to call
		options := client.ClientReadAuthorizationModelOptions{
			AuthorizationModelId: openfga.PtrString(authorizationModelID),
		}
		model, err = fgaClient.ReadAuthorizationModel(context.Background()).Options(options).Execute()

See that this line fails to compile

Sample Code the Produces Issues

See above

Backtrace (if applicable)

N/A

Expected behavior

All methods should be callable.

Additional context

Add any other context about the problem here.

When the Authorization Model ID is `""`, the SDK attempts to send `""` as an I

Description

When the authorization model id is an empty string, and ReadAuthorizationModel is called, it issues a request to:

GET /stores/<store_id>/authorization-models/""

leading to a 404

Version of SDK

latest on main

Version of OpenFGA (if known)

v1.2.0

Expected behavior

If authorization model id is "" when calling ReadAuthorizationModel, it should be treated as nil and throw an Authorization ID is required error
For all other requests, treat it as nil and read latest

Client should be interface

Currently, some of the client interfaces return a struct (such as SdkClientGetStoreRequest). This does not allow easy testing / mocking with the SDKs.

For example,

type SdkClientGetStoreRequest struct {
	ctx    _context.Context
	Client *OpenFgaClient

	options *ClientGetStoreOptions
}

type SdkClientGetStoreRequestInterface interface{
	Options(options ClientGetStoreOptions) SdkClientGetStoreRequestInterface
	Execute() (*ClientGetStoreResponse, error)

    GetContext() _context.Context
    GetOptions() *ClientGetStoreOptions
}

HasContinuationToken() methods return true whereas there is no more page

Hello there,

Description

When using "paginable" methods, the HasContinuationToken() methods return true whereas there is no more page.

I expect the HasContinuationToken() method to return false in such cases.

As the openfga "paginable" endpoints always returns the continuation_token field (with an empty string value if there is no more page), the ContinuationToken response field is never nil (and so the HasContinuationToken() implementation always retrurn true).

Version of SDK

v0.2.2

Version of OpenFGA

v1.1.1

Reproduction

This issue can be reproduced consistently invoking any method querying an openfga "paginable" endpoint.

Here is an example using the ListStores method.

  1. Initialize OpenFgaClient to point an openfga instance with N stores
  2. invoke the ListStores()method with a page size > N
  3. the response HasContinuationToken() should return false but it actually returns true

Sample Code that Produces the Issue

Another example relying on the continuation token to do something with all stores that creates an infinite loop because of the HasContinuationToken() method always returning true.

response, err := fgaClient.ListStores(context.Background()).Options(options).Execute()
if err != nil {
    return "", err
}
doSomethingWith(response.GetStores())

// infinite loop here because HasContinuationToken() always return true
for response.HasContinuationToken() {
    options.ContinuationToken = response.ContinuationToken
    response, err = fgaClient.ListStores(context.Background()).Options(options).Execute()
    if err != nil {
        return "", err
    }
    doSomethingWith(response.GetStores())
}

Expected behavior

The HasContinuationToken() method (on any response type) should return false if the response continuation_token field is an empty string

GoSDK crashes if options are not passed in for Write

When using GoSDK with OpenFGAClient, if we call Write without options defined, SDK will crash due to null pointer. Root cause: WriteExecute tries to read auth model id even when options is null

Code snippet

	if request.options == nil || request.options.Transaction == nil || !request.options.Transaction.Disable {
		writeRequest := openfga.WriteRequest{
			AuthorizationModelId: client.getAuthorizationModelId(request.options.AuthorizationModelId),
		}
          ...
         }

Other endpoints also have the same problem as they read request.options.AuthorizationModelId directly without assertion whether request options is nil.

Example for writeTuples should refer to body rather than requestBody

Description

The README example in Go-SDK uses inconsistent name.

Version of SDK

v0.2.2

Version of OpenFGA (if known)

N/A

OpenFGA Flags/Custom Configuration Applicable

N/A

Reproduction

The README example reads

body := ClientWriteRequest{
    Writes: &[]ClientTupleKey{ {
        User:     "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
        Relation: "viewer",
        Object:   "document:roadmap",
    }, {
        User:     "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
        Relation: "viewer",
        Object:   "document:budget",
    } },
    Deletes: &[]ClientTupleKey{ {
        User:     "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
        Relation: "writer",
        Object:   "document:roadmap",
    } }
}

options := ClientWriteOptions{
    // You can rely on the model id set in the configuration or override it for this specific request
    AuthorizationModelId: openfga.PtrString("01GAHCE4YVKPQEKZQHT2R89MQV"),
}
data, err := fgaClient.Write(context.Background()).Body(requestBody).Options(options).Execute()

Notice that we declare variable as body while the execute refers to requestBody.

README has this inconsistent behaviour in a few places as well

Expected behavior

Should refer to body instead of requestBody.

Additional context

Add any other context about the problem here.

GoSDK client not respecting options and body

Have the following go-code

		body := &client.ClientReadChangesRequest{
			Type: selectedType,
		}
		options := &client.ClientReadChangesOptions{
			ContinuationToken: &continuationToken,
		}

		response, err := fgaClient.ReadChanges(context.Background()).Body(*body).Options(*options).Execute()

Capture from server sides shows that the body and continuation token not passed to the request

Cannot make calls to OpenFGA when exposed under path

Description

In a lot of our deployments we use path based routing (e.g. example.com/serviceA proxies to service A, example.com/serviceB proxies to service B). It seems like the SDK does not support calling OpenFGA when it is exposed under a path (https://github.com/openfga/go-sdk/blob/main/utils.go#L336).

Reproduction

  1. Initialize OpenFGA server
  2. Use a reverse proxy to expose OpenFGA under a path
  3. Try to make calls to the server through the proxy using the SDK

Expected behavior

We would like to be able to use the SDK to make calls to an OpenFGA server that is exposed under a path.

README content display format is incorrect

Checklist

  • I have looked into the README and have not found a suitable solution or answer.
  • I have looked into the documentation and have not found a suitable solution or answer.
  • I have searched the issues and have not found a suitable solution or answer.
  • I have upgraded to the latest version of OpenFGA and the issue still persists.
  • I have searched the Slack community and have not found a suitable solution or answer.
  • I agree to the terms within the OpenFGA Code of Conduct.

Description

image
README is broken after this code block. and it is caused by missing close tag at L931

Expectation

image
It supposes to look like this

Reproduction

Open github.com/openfga/go-sdk page and scroll to here

OpenFGA SDK version

0

OpenFGA version

0

SDK Configuration

0

Logs

No response

References

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤ī¸ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.