LykaiosNZ / SpiceWeaver

SpiceWeaver is a .NET library for working with SpiceDB Schemas
MIT License
3 stars 1 forks source link

SpiceWeaver goals? #18

Closed tanczosm closed 4 months ago

tanczosm commented 4 months ago

Greetings! I'm working on a C# client for SpiceDb and I sorely would love to automate the process of making relationships and permissions strongly-typed and happened upon your repo. Hand-writing everything requires constant referencing of the schema and I'd rather not do that if possible.

My repo is at https://github.com/JalexSocial/SpiceDb

I was wondering what your project goals were if you would be willing to share.

LykaiosNZ commented 4 months ago

Howdy! I'm already familiar with your repo and using it at work - have even raised a couple of issues under my work account (thanks for addressing those btw) 😀

In terms of goals, the first one was to solve the problem of having to refer to the schema itself for definition/relation/permission names and referencing these directly or manually creating constants for them. So at present the source generator just generates these constants for you:

namespace SpiceWeaver
{
    public static class Schema
    {
        public static class Definitions
        {
            public static class User
            {
                public const string Name = "user";
            }

            public static class Document
            {
                public const string Name = "document";
                public static class Relations
                {
                    public const string Viewer = "viewer";
                    public const string Editor = "editor";
                }

                public static class Permissions
                {
                    public const string View = "view";
                    public const string Edit = "edit";
                }
            }
        }
    }
}

Ideally, it would evolve into a strongly-typed client that wraps a lower-level client like yours, so consumers would not need to work directly with ResourceReference etc. I haven't given too much thought as to exactly how this would look just yet.

If you think this is something that could live under the SpiceDb.net project, I'd be more than happy to contribute.

One thing to note is that originally I was writing a schema parser myself, but it was using Pidgin which is not compatible with .NET Standard (and therefore Source Generators), so I scrapped that and am now using spice2json instead, which uses the SpiceDB DSL parser to output json. This does mean there is a dependency on this external binary at present, however.

I'm unsure if the Source Generator route is 100% necessary versus something like a simple cli tool that spits out .cs files, but I figured it may be worthwhile for people able to reference and work with the schema file directly. It's a bit of a catch-22 though, given its limitation of targeting .NET Standard.

tanczosm commented 4 months ago

Ah that's how your named looked familiar. I didn't put the two together. Since the recent major refactor you can now supply a ChannelBase directly if so desired for the grpc channel. Try out 1.5.1 if you haven't yet. Relationships can also include caveats now when you create them. I also reorganized a lot of code to clean things up a bit and started work on a documentation project.

If we could unite the two I think that would be great and since you are using it for work it could give you an opportunity to shape the library as well. Honestly it feels like a bit of a lonely island working on the client because it's tough to tell how much SpiceDb is even adopted by the .net community. TBH I saw your comment on the spice2json library so it appears it isn't just limited to .net. I made the .net client out of necessity originally though because I wanted to use it from c# first of all, there wasn't a lot to choose from client-wise, and I wanted to create a wrapper for possibly more than SpiceDb (which is why some of the types don't match exactly in the client) but I've since mostly steered exclusively towards SpiceDb. I do want to be able to provide better quality of life c# code for developers though who want to work with the database though which means if I can create a useful layer on top of the grpc client that could be useful.

One of the aspects I found off-putting with libraries like the Java client is how much code it takes to just check a permission.

For your project, what if you still used spice2json but eliminated the executable dependency (sort of) by just requiring that schemas be run through spice2json first and named with .zedj (or similar) before adding to the project? Then have the source generator just transform that specific file? That way you can just check in the transformed schema directly into your source. It requires an extra step but the ability to use it doesn't require much setup work (especially on remote build servers).

Here is an ANTLR grammar I made. It is missing types for caveats but is a start. The grammar can be used as a basis to build other grammars. Right now I think the spice2json route is the best approach rather than trying to parse it directly.

grammar SpiceDb;

// Parser Rules

schema: (definition | caveat | comment)*;
definition: 'definition' ID '{' definitionBody '}';
definitionBody: (relation | permission | comment)*;
relation: 'relation' ID ':' subject ( '|' subject )*;
permission: 'permission' ID '=' expression;
caveat: 'caveat' ID '(' parameters ')' '{' expression '}';
expression: expression ('+' | '&' | '-' | '->') expression
          | ID
          | '(' expression ')';

parameters: parameter (',' parameter)*;
parameter: ID type;

type: 'int' | 'string' | ID; 

subject: typeReference (subRelation | wildcard)?;
typeReference: ID;
subRelation: '#' ID;
wildcard: ':*';

comment: DOC_COMMENT | LINE_COMMENT | BLOCK_COMMENT;

// Lexer Rules
ID: [a-zA-Z_][a-zA-Z_0-9]*;
DOC_COMMENT: '/**' .*? '*/' -> channel(HIDDEN);
LINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN);
BLOCK_COMMENT: '/*' .*? '*/' -> channel(HIDDEN);
WS: [ \t\r\n]+ -> channel(HIDDEN);

Let me know your thoughts.

tanczosm commented 4 months ago

Does Spice2Json pick up on caveats as well? A caveat has a name and context, but the context keys (user_ip and allowed_range) are also well-defined.

ex.

caveat has_valid_ip(user_ip ipaddress, allowed_range string) {
  user_ip.in_cidr(allowed_range)
}

For simplicity sake you could create a class called Caveats that contains all the caveats. If the caveats themselves were classes that inherited SpiceDb.net Caveat that would make working with them pretty simple.

new Caveats.HasValidIp { AllowedRange = "10.20.30.0" }

That would be something like:

public class Caveats {
   public class HasValidIp : Caveat
   {
       public HasValidIp () { this.Name = "has_valid_ip"; }
       public IpAddress? UserIp { 
          get { 
              return this.Context.ContainsKey("user_ip") ? this.Context["user_ip"].ToIpAddress() : null
          }
          set {
              this.Context["user_ip"] = value;
          }
        }
       public string AllowedRange {
           get {
             return this.Context.ContainsKey("allowed_range") ? this.Context["allowed_range"] : null;
           }
           set {
               this.Context["allowed_range"] = value;
           }
       }
   }
}

Note that allowed_range would be supplied when creating the relationship caveat and user_ip could be supplied during permission checks.

LykaiosNZ commented 4 months ago

For your project, what if you still used spice2json but eliminated the executable dependency (sort of) by just requiring that schemas be run through spice2json first and named with .zedj (or similar) before adding to the project?

I've already taken this into account with an option on the additional file, was just missing documentation to explain

Here is an ANTLR grammar I made. It is missing types for caveats but is a start. The grammar can be used as a basis to build other grammars. Right now I think the spice2json route is the best approach rather than trying to parse it directly.

There's also a tree-sitter grammar written by the co-founder of AuthZed but I found tree-sitter hard to work with.

LykaiosNZ commented 4 months ago

An issue was just raised in the SpiceDB repo about having an official .NET client: https://github.com/authzed/spicedb/issues/1877

Be interesting to see what the response to this is.

tanczosm commented 4 months ago

TBH it's pretty easy to just generate a c# client off the protobuf definitions but I could definitely see how it's easier to have faith in a company if the client is supported by that company. You just have to deal with all the custom google grpc data structures.

I wouldn't have bothered making what I did if there was something official.

tanczosm commented 4 months ago

I'm playing around with the source generator and it's working well. I created a new Relationship object as follows:

using Arch = Archimedes.Schema.Definitions;

new Relationship($"{Arch.Document.Name}:abc123", Arch.Document.Relations.Viewer, $"{Arch.User.Name}:efg456");

Using the definition with an id is extremely common.. maybe add an additional "WithId" method to each definition as a helper? Something generated like this:

public static class User
{
    public const string Name = "user";
    public static string WithId (string id) => $"user:{id}";
}

Then code to use it is:

using Arch = Archimedes.Schema.Definitions;

new Relationship(Arch.Document.WithId("abc123"), Arch.Platform.Relations.Viewer, Arch.User.WithId("efg456"));

Thoughts?

LykaiosNZ commented 4 months ago

I like it, if you throw that in a new issue I can take a look tomorrow - should be pretty trivial