Conversation
d9821d2 to 4fd65a0 Compare | Thanks @ueffel! |
| The |
| It may be interesting to try implementing Kerberos authentication without wrapping the http.Transport (which is need with github.com/dpotapov/go-spnego) |
| I think there should be a way to create an func NewEmptyAuth() Authorizer { fmap := make(map[string]AuthFactory) az := &authorizer{fmap, sync.Mutex{}, &nullAuth{}} return az }And maybe one func NewPreemptiveAuth(auther Authenticator) Authorizer { fmap := make(map[string]AuthFactory) az := &authorizer{fmap, sync.Mutex{}, auther} return az }Minor inconvience Sample kerberos authenticationpackage main import ( "net/http" "os" "github.com/dpotapov/go-spnego" "github.com/studio-b12/gowebdav" ) type krb5Auth struct { sspi spnego.Provider NoCanonicalize bool } func NewKrb5Auth() *krb5Auth { return &krb5Auth{sspi: spnego.New()} } func (k *krb5Auth) Authorize(c *http.Client, rq *http.Request, method string, path string) error { return k.sspi.SetSPNEGOHeader(rq, !k.NoCanonicalize) } func (k *krb5Auth) Verify(rq *http.Request, rs *http.Response, method string, path string) (bool, error) { if rs.StatusCode == http.StatusUnauthorized { return false, &os.PathError{ Op: "Authorize", Path: path, Err: gowebdav.StatusError{Status: rs.StatusCode}, } } return false, nil } func (k *krb5Auth) Clone() gowebdav.Authenticator { auther := NewKrb5Auth() auther.NoCanonicalize = k.NoCanonicalize return auther } func (k *krb5Auth) Close() error { return nil } var _ gowebdav.Authenticator = (*krb5Auth)(nil)Usage like this authorizer := gowebdav.NewEmptyAuth() authorizer.AddAuthenticator("negotiate", func(rq *http.Request, rs *http.Response, method, path string) (auth gowebdav.Authenticator, err error) { return NewKrb5Auth(), nil })or authorizer := gowebdav.NewPreemptiveAuth(NewKrb5Auth()) |
| awesome! func NewBasicAuth(login, secret string) Authorizer { return &basicAuthAuthorizer{&BasicAuth{login, secret}} } |
Yeah, but this requires far more code 😇. Also I thought about what would happen, if a server offers multiple ways to authenticate (multiple (Edit: wouldn't work) |
I LIKE U
|
we will take care about that too. |
| @chripo |
but this still only looks at the first authentication header |
| In fact, I was looking up in the wrong docs, somehow... |
| Well, I'm done so far. What needs to be done to get merged:
|
| thank you again @ueffel! Absolut Fett! |
The changes simplify the `req` method by moving the authentication-related code into the API. This makes it easy to add additional authentication methods. The API introduces an `Authorizer` that acts as an authenticator factory. The authentication flow itself is divided down into `Authorize` and `Verify` steps in order to encapsulate and control complex authentication challenges. The default `NewAutoAuth` negotiates the algorithms. Under the hood, it creates an authenticator shim per request, which delegates the authentication flow to our authenticators. The `NewEmptyAuth` and `NewPreemptiveAuth` authorizers allow you to have more control over algorithms and resources. The API also allows interception of the redirect mechanism by setting the `XInhibitRedirect` header. This closes: #15 #24 #38
| @zekroTJA Do you have time for a feature review? |
Yeah, I would take a look into it in the next couple of days if that fits. I am not that deep into the WebDav protocol itself but I'll take a look into it. :) |
zekroTJA left a comment
There was a problem hiding this comment.
Looks good so far. Just some code style things and proposes for avoiding panics in the package.
Some of my annotations are based on personal preference and may be a bit nitpicky, but I am open for discussion. :)
| } | ||
| | ||
| // Do not Clone the shim, it ends badly. In any case for you. | ||
| func (s *authShim) Clone() Authenticator { |
There was a problem hiding this comment.
I would strongly avoid panicing in packages. Especially in a method which is "hidden" behind an interface. I would split the Authenticatior interface into something like the following.
type Authenticator interface { Authorize(c *http.Client, rq *http.Request, path string) error Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) io.Closer } type ClonableAuthenticator interface { Authorize(c *http.Client, rq *http.Request, path string) error Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) io.Closer Clone() ClonableAuthenticator }Another approach would be to make Authenticators not clonable by default, adding a Clonable interface and checking the clonability when required.
type Authenticator interface { Authorize(c *http.Client, rq *http.Request, path string) error Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) io.Closer } type Clonable interface { Clone() interface{} }Then, the clone call could look something like this.
var a, cloned Authenticator = // ... if ac, ok := a.(Clonable); ok { cloned = a.Clone().(Authenticator) }Another approach would be to let Clone() return the cloned Authenticator as well as an error. Then, this implementation could return an "not implemented" error. But actually, I am not a huge fan of introducing an error return just for the sake of covering "not implemented" errors.
There was a problem hiding this comment.
Thanks for the suggestions.
I'm not so comfortable with it because it adds more code and introduces checks in other parts of our code.
In this particular case, it now returns a noAuth instance instead of a panic.
| | ||
| // AddAuthenticator A preemptive authorizer may only have a single authentication method | ||
| func (b *preemptiveAuthorizer) AddAuthenticator(key string, fn AuthFactory) { | ||
| panic("You're funny! A preemptive authorizer may only have a single authentication method") |
There was a problem hiding this comment.
Same here like with Clone. Maybe proceed here as I proposed above.
There was a problem hiding this comment.
What would an user expect?
b:= &BasicAuth{user: "user", pw: "password"} a:= NewPreemptiveAuth(b) a.AddAuthenticator("fooAuth", func(...) (auth Authenticator, err error) { return &fooAuth{...}, nil })In this particular case I'm opt for the panic, because it's only a convenience method which has to be setup manually.
| Hey, sorry to bother you. Could you get this resolved and merged? This is needed for #70 which in turn would benefit a lot of projects downstream (e.g. rmfakecloud). |
The main goal was to simplify the
reqmethod inrequest.goand making it easier to add more authentication methods.All the magic went into the
auth.gofile.This feature introduces an
Authorizerwhich acts as anAuthenticatorfactory. Under the hood it creates an authenticatorshim per request, which delegates the authentication flow
to our authenticators.
The authentication flow itself is broken down into
Authorize' andVerify' steps to encapsulate and control complex authentication challenges.Furthermore, the default
NewAutoAuthauthenticator can be overriddenby a custom implementation for more control over flow and resources.
The
NewBacisAuthAuthorizer gives us the feel of the old days.