osohq / oso

Oso is a batteries-included framework for building authorization in your application.
https://docs.osohq.com
Apache License 2.0
3.47k stars 176 forks source link

Would be interested in Clojure/Clojurescript libraries #407

Open rberger opened 4 years ago

rberger commented 4 years ago

This would address some issues we're having in our Clojurescript / Clojure Apps. Theoretically could wrap the Node/java libraries.

samscott89 commented 4 years ago

Thanks for opening an issue! If there's an enough interest, we would love to do Clojure/Clojurescript libraries at some point.

In the meantime, however, if you are interested in using the Node/Java libraries we'd be happy to try and help you get that working.

joshrotenberg commented 2 years ago

I'm playing around with this a bit. It looks like it's easy enough to just use some interop and wrap the Java calls in Clojure functions, but I think there is probably a nicer way (still wrapping the Java but maybe offering a higher level Clojure API). I'll report back here if I come up with something interesting.

joshrotenberg commented 2 years ago

So for the absolute simplest case (when your rules don't need to call any methods), registering simple maps appears to work just fine:

(ns oso-demo
   (:import com.osohq.oso.Oso java.util.Map))

(defn new-oso []
   (Oso.))

(defn register-class [oso c name]
   (doto oso
      (.registerClass c name)))

(defn load-files [oso paths]
   (doto oso
     (.loadFiles (into-array String paths))))

(let [oso (new-oso)]
    (doto oso
      (register-class Map "User")
      (register-class Map "Widget")
      (register-class Map "Company")
      (load-files ["test_oso.polar"]))
      (let [user {"name" "guest"}
            widget {"id" 1}]
       (assert (.isAllowed oso user "get" widget))))

To be continued ...

joshrotenberg commented 2 years ago

So my next inclination was to try to use defrecord/defprotocol for the type which would allow sort of a hybrid approach: the User being a record could have map key/values but implement a protocol for the methods. Unfortunately Records end up with keyword keys for the maps which Host.java doesn't like:

 Unhandled com.osohq.oso.Exceptions$UnexpectedPolarTypeError
 Cannot convert map with non-string keys to Polar

The next option is to create real Java classes for the types. Could possibly be done with gen-class but that requires ahead of time compilation which is kind of a bummer.

--

This is all bottom up so far, but thinking about it from a top down approach, what would the Clojure API actually look like? Obviously the thin wrapper around Java interior above is definitely a possibility, but I think a data driven approach would be way more idiomatic and could potentially hide some of the class creation stuff if possible. I've been toying with malli a little bit lately for something else, and I like that approach. We'd probably want to create some kind of wrapper or maybe an alternative record class that supports string keys and then generate a protocol for all the specified methods. This can possibly implicitly do all of the registering as well. We still need to fit the file or string load in somewhere too. I'm going to go sit in the hammock on this for a bit.

joshrotenberg commented 2 years ago

Going to track this work here for now, it's easier with a real project: https://github.com/joshrotenberg/oso-clj