Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should the function generateWorld be accessible ? #147

Open
qwackididuck opened this issue Sep 17, 2024 · 7 comments
Open

Should the function generateWorld be accessible ? #147

qwackididuck opened this issue Sep 17, 2024 · 7 comments

Comments

@qwackididuck
Copy link

qwackididuck commented Sep 17, 2024

Hi,

We're in charge of taking over a component that generates and consumes biscuits.
The component is developed in Java and we want to rewrite it in Go.

I wanted to retrieve a fact from a unmarshalled biscuit.

Example in Java of retrieving the facts (here, the first one)

biscuit.context().stream().filter(Option::isDefined).map(Option::get).findFirst()
.orElseThrow(() -> forbidden());

And the biscuit

user(1233);

In Go, we have no function that allows us to get the facts of a biscuit...

I suppose it could be done by performing Query on the World structure, and the Biscuit structure has a generateWorld method that can create this needed World, but it is private and only used by the tests...

func (b *Biscuit) generateWorld(symbols *datalog.SymbolTable) (*datalog.World, error) {
	world := datalog.NewWorld()

	for _, fact := range *b.authority.facts {
		world.AddFact(fact)
	}

	for _, rule := range b.authority.rules {
		world.AddRule(rule)
	}

	for _, block := range b.blocks {
		for _, fact := range *block.facts {
			world.AddFact(fact)
		}

		for _, rule := range block.rules {
			world.AddRule(rule)
		}
	}

	if err := world.Run(symbols); err != nil {
		return nil, err
	}

	return world, nil
}

Is this a bug ?
Shouldn't it be available for consumption ?

If not, how can I retrieve the facts of an unmarshalled biscuit properly ? (no regex and other ugly stuff like that)

Thank you very much

@Benoit12345
Copy link

I think it need to be ! Today, ther's no way to compare 2 biscuits or just read any facts...

Benoit12345 added a commit to Benoit12345/biscuit-go that referenced this issue Sep 18, 2024
@qwackididuck
Copy link
Author

qwackididuck commented Sep 23, 2024

Any update on this subject ?

The solution suggested by @Benoit12345 is exactly what I thought.

Do you agree ?

@Geal
Copy link
Contributor

Geal commented Sep 24, 2024

could you tell me more about the use case here? The generateWorld was meant only for tests. if you want to access data in the token, that's supposed to be done through the authorizer API, with methods like Query.
A better solution to access the entire list would be the snapshot API that we will introduce soon to this library (already specified and implemented in other libraries): https://github.com/biscuit-auth/biscuit/blob/c87cbb5d778964d6574df3e9e6579567cad12fff/schema.proto#L186-L207
If the goal is to compare tokens there may be other means. What do you want to achieve?

@Benoit12345
Copy link

Thanks @Geal,
One of the use case (the main one for us) is for our own tests where we have to check a generated token (ie. same as expected one). For the moment, we use a "very dirty way" by comparing the String() result (which generate sometimes different output and failed our tests with no reason... but that's another subject).
We thought to use the authorizer API but using it means that we have, for each generated biscuit, for each use cases, for each facts/rules, to validate every possible values to be sure that the fact/rule is present and validate the right way. That's much more a validation of the authorizer part than a validation of the biscuit itself.
Today, for the Go client (it doesn't seem to be the case for other clients), the API is really limited to creation/authorization. It's quite hard to just consult the biscuit content.

@qwackididuck
Copy link
Author

Thank you @Geal for your answer

To add more information to @Benoit12345 comment about our use cases :

We have different kinds of biscuit. Each kind allowing access to different kind of resources.
Since they are different, they have different authorizers, that are dynamically loaded depending on the type.
The type of the biscuit is stored inside of it.

So we can not use the Query API on the authorizer to get the type, since we need the type to get the authorizer 😞
In my understanding, the Query API is available on an authorizer, after an Authorize() call.

In my tests :

Query before authorizing

        b, err := biscuit.Unmarshal(token)
	if err != nil {
		panic("unmarhsal failed: " + err.Error())
	}

	fmt.Println(b.String())
	authorizer, err := b.Authorizer(pub)
	if err != nil {
		panic("authorizer creation failed: " + err.Error())
	}
	
	authorizerRules, err := parser.FromStringRule(`
		my::type($t) <- type($t)
	`)
	if err != nil {
		panic("authorizer contents can not be parsed: " + err.Error())
	}

	authorizer.AddRule(authorizerRules)

	fs, _ := authorizer.Query(authorizerRules)  // fs = [] here

Query after authorizing

        b, err := biscuit.Unmarshal(token)
	if err != nil {
		panic("unmarhsal failed: " + err.Error())
	}

	fmt.Println(b.String())
	authorizer, err := b.Authorizer(pub)
	if err != nil {
		panic("authorizer creation failed: " + err.Error())
	}
	
	policy, err := parser.FromStringPolicy("allow if type(\"A\")")
	if err != nil {
		panic("unparsable policy: " + err.Error())
	}
	
	authorizer.AddPolicy(policy)

	if err := authorizer.Authorize(); err != nil {
		panic("failed authorizing: " + err.Error())
	}

	authorizerRules, err := parser.FromStringRule(`
		my::type($t) <- type($t)
	`)
	if err != nil {
		panic("authorizer contents can not be parsed: " + err.Error())
	}

	authorizer.AddRule(authorizerRules)

	fs, _ := authorizer.Query(authorizerRules)  // fs contains the my::type fact

As said in my first message, we have to work with this legacy approach...

With an imaginary example :

  1. We have 2 kinds of biscuit :
    Type A with the following authorizer

    check if res::user($r), user($u), $u == $r
    

    Type B with the following authorizer

    check if res::amount($a), max($m), $a <= $m
    
  2. We generate a biscuit for token A

    type("A")
    user("foo")
    
  3. The biscuit is used by the client to access a resource, so to get the authorizer, we must retrieve the fact type from the given biscuit.

So how can we do it in your opinion ?

Thank you very much :)

@qwackididuck
Copy link
Author

Hello @Geal ,

Do you have any advice on our issue ?

We need to keep the same behaviour, allowed in Java, but it does not seem possible in Golang without the requested evolution..

Or maybe you have a better idea ?

Thank you !

@qwackididuck
Copy link
Author

Hello,

Does anyone have an opinion ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants