biscuit-auth / biscuit-go

Apache License 2.0
70 stars 21 forks source link

Cannot get authorizer Query to return matching tuples #131

Closed pcolmer closed 8 months ago

pcolmer commented 8 months ago

We're implementing a platform that has granular rights across its APIs. A biscuit might have these rights encoded like this:

right("spire", "org:5859403d-8105-4487-974f-f258dab93fb8", "read:org");
right("spire", "org:090c2636-77ab-4915-9922-c66c03b2afc8", "read:org");
right("spire", "org:139875db-56fb-461e-8ba9-6ea29b03f581", "read:org");

An API that provides a "read" feature on a specific org can easily verify that the caller has permission to do so by building an authorizer that looks for the specific right with the correct org UUID and the read:org permission.

However, an API that provides a "list" feature needs to be able to determine which orgs the caller has permission to list. If I've read https://doc.biscuitsec.org/usage/go#query-data-from-the-authorizer correctly, it should be the last code sample on that page ... except I cannot get it to work.

I've written a very simple Go test routine, based on the code from https://doc.biscuitsec.org/usage/go:

package main_test

import (
    "crypto/ed25519"
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "testing"

    "github.com/biscuit-auth/biscuit-go/v2"
    "github.com/biscuit-auth/biscuit-go/v2/parser"
)

func TestCreateBiscuit(t *testing.T) {
    rng := rand.Reader
    publicRoot, privateRoot, _ := ed25519.GenerateKey(rng)

    authority, err := parser.FromStringBlock(`
    right("spire", "org:5859403d-8105-4487-974f-f258dab93fb8", "read:org");
    right("spire", "org:090c2636-77ab-4915-9922-c66c03b2afc8", "read:org");
    right("spire", "org:139875db-56fb-461e-8ba9-6ea29b03f581", "read:org");
`)

    if err != nil {
        panic(fmt.Errorf("failed to parse authority block: %v", err))
    }

    builder := biscuit.NewBuilder(privateRoot)
    builder.AddBlock(authority)

    b, err := builder.Build()
    if err != nil {
        panic(fmt.Errorf("failed to build biscuit: %v", err))
    }

    token, err := b.Serialize()
    if err != nil {
        panic(fmt.Errorf("failed to serialize biscuit: %v", err))
    }

    // token is now a []byte, ready to be shared
    // The biscuit spec mandates the use of URL-safe base64 encoding for textual representation:
    t.Log(base64.URLEncoding.EncodeToString(token))

    authorizer, err := b.Authorizer(publicRoot)
    if err != nil {
        panic(fmt.Errorf("failed to verify token and create authorizer: %v", err))
    }
    rule, err := parser.FromStringRule(`data($service, $id, $permission) <- right($service, $id, $permission)`)
    if err != nil {
        panic(fmt.Errorf("failed to parse check: %v", err))
    }
    t.Log(authorizer.Query(rule))
}

but the output from authorizer.Query(rule) is just [] <nil>

Am I doing something wrong or am I not able to retrieve the rights in this manner?

divarvel commented 8 months ago

this is definitely not the expected behaviour, i'll have a closer look at your code to try and find out the issue.

edit: the example queries the authorizer after running Authorize, that might be the cause. biscuit-rust allows querying a biscuit before running authorization, but maybe the go lib doesn't. you can try running authorize (and discard the result) before querying.

pcolmer commented 8 months ago

@divarvel

but maybe the go lib doesn't. you can try running authorize (and discard the result) before querying.

I'm a bit confused by this suggestion. The Query function is only available via the Authorizer ... so don't I have to create the authorizer first in order to then call Query?

That last bit of code was pretty much taken straight from website_test.go so I'm not sure how else to tackle this.

divarvel commented 8 months ago

The way the library is currently designed is that it expects the querying to be done once the authorization step has been performed.

If you need to query the token before the authorization, a solution for now is to create a throwaway authorizer, load the token into it, run authorization (it will fail but you care about the final state about the authorizer), and then query the authorizer.

A better way would be to do what the rust library does: allow querying an authorizer before running authorization checks, but the go library does not do it yet. I can provide guidance on implementing it though.

pcolmer commented 8 months ago

@divarvel Ah! I've got it now! Sorry for being a bit slow on understanding this.

Thanks - calling the authorization step first does indeed allow the querying to then work.