-
Notifications
You must be signed in to change notification settings - Fork 97
Improves the authentication flow #71
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
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:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are on a very good way to make this awesome :)
Also: At least the multiAuthTest from my branch made it :D
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. :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here like with Clone
. Maybe proceed here as I proposed above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
req
method inrequest.go
and making it easier to add more authentication methods.All the magic went into the
auth.go
file.This feature introduces an
Authorizer
which acts as anAuthenticator
factory. 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' and
Verify' steps to encapsulate and control complex authentication challenges.Furthermore, the default
NewAutoAuth
authenticator can be overriddenby a custom implementation for more control over flow and resources.
The
NewBacisAuth
Authorizer gives us the feel of the old days.