Fork me on GitHub
#yada
<
2016-08-26
>
kurt-yagram07:08:13

I'm a bit confused how :access-control works. I got it working more or less how I want, but still... I've got in a yada/resource:

:access-control {:scheme :myns/myscheme
                     :verify (fn [m] (Logger/info (str "verify-fn: " m)))
                     :authorization {:methods {:get "role/user"
                                               :put "role/user"}}}
 
Which means: a user has to have role role/user to have access to get and put methods. This works as expected. From myns/myscheme, I return nil or a map with :user and :roles keys, or a map with :type and cause keys, or I throw an exception. All well and fine, but 1. I don't see what's the use of :verify. It seems it is never called. What's the use of verify in this case? 2. For forbidden access, can I pass a message? 3. What if I want access-control to comply with 2 schemes? (Is that possible at all? For example: the output of the first one, is piped to the second one etc? - Or so I define a scheme and compose them via functions?)

malcolmsparks08:08:44

1. The verify function is used by some auth schemes to call back into the resource. It's not applicable to every scheme. Check the implementation of basic auth if you're unsure.

malcolmsparks08:08:06

3. Authentication schemes are a disjunction (either/or). Multiple authentication schemes allows the user agent to pick one that is suitable. For example, a human might want to authenticate by sending a secret lodged in a brain, a script might want to use an access token. You shouldn't try to implement MFA, 2FA by having 2 auth schemes, rather, you should have one auth scheme that implements the 2FA.

kurt-yagram08:08:31

allright... think I get it.

malcolmsparks08:08:50

@kurt-yagram let me know if this helps or you need more detail. The HTTP standard is still a little ambiguous in the area of multiple realms and multiple auth-schemes, I've interpreted it as best I can.

malcolmsparks08:08:29

The code is pretty confusing actually

kurt-yagram08:08:28

Yeah, I'm just trying to get my head around it. It starts to work out bit by bit. I might come back later, but especially point 1 and 3 where important. Got them solved (more or less).

malcolmsparks08:08:34

The defmulti is called verify too, which could be the source of confusion. yada calls this defmulti which dispatches to the (yada) verify function for "Basic". (or whatever scheme you're using)

kurt-yagram08:08:49

I was implementing JWT stuff - yeah, that's where my confusion came from.

malcolmsparks08:08:51

The basic implementation just happens to ALSO have a key called 'verify' which the implementation calls later on.

malcolmsparks08:08:09

The 2 verify functions are named the same but are different - gosh that's confusing, sorry!

kurt-yagram08:08:12

Right... that's the most confusing part.

malcolmsparks08:08:47

I struggled to find a better name at the time

malcolmsparks08:08:50

Naming is hard 馃槥

kurt-yagram08:08:57

yeah... it is.

malcolmsparks08:08:35

But I should rename ,but can't do so now without breaking compatibility - because if I change the yada one then people who have added their own defmethods will complain, and if I change the latter one I break the resource model

kurt-yagram08:08:23

Now, just another one: if I return a map like :

{:type :validation
 :cause :whatever}
in my defmulti verify, what exactly happens? I know I will not have access, but what's the use of the :type and cause keys?

malcolmsparks08:08:30

There's still quite a lot more docs on the way for security, so I'll make pains to explain what's going on

malcolmsparks08:08:01

let me check, iirc it is merged with the context under :authorization

malcolmsparks08:08:16

{:type :validation :cause :whatever} are the credentials, yada calls (assoc-in ctx [:authentication realm] credentials) so they appear in the context (be careful of the realm, it defaults to "default")

malcolmsparks08:08:36

Credentials established in one 'realm' should not interfere with credentials established in another

kurt-yagram08:08:32

ok, so far, so good... Oh ok, and that context I might use in status responses? (Since they are not valid, I'll return status 403)

kurt-yagram08:08:42

- I don't use the realms for now

malcolmsparks08:08:11

I can't imagine anyone uses multiple realms, but I've constrained myself to implementing the standard rather than listening to users

malcolmsparks08:08:23

If credentials cannot be established (because the user can't be verified, like a bad password was entered, etc.) then you return a 401

malcolmsparks08:08:35

If the credentials are established OK, but then you decide later on (in authorization) that the credentials are sufficient for access to the resource, then you return 403

kurt-yagram08:08:47

oh yes, right...

malcolmsparks08:08:19

it's a painfully subtle distinction 馃槥

kurt-yagram08:08:36

and in the 403, I always get:

"error": {
    "error": "clojure.lang.ExceptionInfo: Forbidden {:status 403, :headers {}}",
    "data": "{:status 403, :headers {}}"
  }
Well, it's a logical distinction

malcolmsparks08:08:37

Have you tried status responses? Adding {:responses {403 {:response ...}}} to the resource model?

malcolmsparks08:08:03

If it doesn't work, I'll treat that as a bug and fix

malcolmsparks08:08:24

Because this is also tied up with the error interceptor chain, I'm pretty sure it will work

kurt-yagram08:08:51

nono, not yet, that's the next step. But in the basic implementation, that's the result:

{
  "status": 401,
  "message": "Unauthorized",
  "id": "yada/resource id",
  "error": {
    "error": "clojure.lang.ExceptionInfo: No authorization provided {:status 401, :headers {}}",
    "data": "{:status 401, :headers {}}"
  }
}
(similar for 403)

kurt-yagram08:08:13

well, not basic, but in the default implementation.

malcolmsparks09:08:38

@kurt-yagram are you expecting something different? I'm not sure if you're asking me a question here

kurt-yagram09:08:29

nono, just a general note. At first, I expected I would be able to add something to it (without using status-responses). I just need to use status-responses for customized messages. All clear for me, thx.

imre13:08:32

@malcolmsparks is there any support in yada to parse the different parts of a multipart form?

imre13:08:09

so say one part is an application/json - will that be resolved into a map for me?

malcolmsparks13:08:33

Yes. All supported. See phonebook update which uses a multipart upload.

imre13:08:40

hmm not sure I'm looking in the right place but I can't find something that looks like this: http://stackoverflow.com/a/9082243

kurt-yagram13:08:00

Hey, is patch supported yet? - it seems not but I ask to be sure.

malcolmsparks13:08:11

No, not yet. You can add your own implementation though

kurt-yagram13:08:28

Allright... saw that. Thanks

imre14:08:10

Patch is messy though, so good luck!

imre16:08:25

still trying to get this uber-multipart working

imre16:08:49

this is a sample request I'm sending:

--wdgcgP44AnKGDAdw_3zrrc9tQzvKW5OUVk
Content-Disposition: form-data; name="baz"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit

5
--wdgcgP44AnKGDAdw_3zrrc9tQzvKW5OUVk
Content-Disposition: form-data; name="qux"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{"quux":1,"asdf":{"zxcv":"yoyo"}}
--wdgcgP44AnKGDAdw_3zrrc9tQzvKW5OUVk--

imre17:08:28

and the method trying to catch it is:

{:post
 {:parameters {:body {:baz sc/Str
                      :qux {:quux sc/Int
                            :asdf {:zxcv sc/Str}}}}
  :consumes   "multipart/form-data"
  :produces   "text/plain"
  :response   (fn [ctx]
                (clojure.pprint/pprint (-> ctx :parameters :body))
                "")}}

imre17:08:38

basically I want to abuse multipart in a way that I can send structured data in one of the fields

imre17:08:14

reason is that I need an endpoint where I can put a multi-level structure plus a file at the same time

imre17:08:26

I'll keep searching

imre17:08:44

hmm, maybe PartConsumer is my friend

malcolmsparks17:08:11

Possibly! It's a stretch feature but I could be your friend too :)

imre21:08:23

I'm sure that would be helpful 馃檪

imre22:08:23

I haven't yet managed to get to the bottom of it, but it looks like parameter coercion works different in multipart than outside

imre22:08:08

for example even using a simple :parameters {:body {:baz sc/Int}}

imre22:08:00

fails for a request of

--wdgcgP44AnKGDAdw_3zrrc9tQzvKW5OUVk
Content-Disposition: form-data; name="baz"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit

5
--wdgcgP44AnKGDAdw_3zrrc9tQzvKW5OUVk--

imre22:08:27

with {:baz (not (integer? a-yada.multipart.DefaultPart))}

imre22:08:50

whereas string->int coercion works in the "normal" cases (json,edn etc)

imre22:08:30

what seems to be missing is the ability to define the allowed "sub-content-types" that make up the multipart request

imre22:08:47

for example:

imre22:08:48

:form (or body) has the following fields: :count which coerces from text/plain to sc/Int :name which coerces from text/plain to sc/Str (I only managed to get this one working so far) :file which is an uploaded file coming as application/octet-stream for example :foo-bar for which we accept application/json, application/edn etc. and should coerce to say

{:aa sc/Int
 :bb sc/Int
 :cc {:dd sc/Int}}