Feat: Authentication API
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
This commit is contained in:
committed by
Christoph Polcin
parent
3282f94193
commit
ca40e2802e
90
client.go
90
client.go
@@ -10,57 +10,39 @@ import (
|
||||
"os"
|
||||
pathpkg "path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const XInhibitRedirect = "X-Gowebdav-Inhibit-Redirect"
|
||||
|
||||
// Client defines our structure
|
||||
type Client struct {
|
||||
root string
|
||||
headers http.Header
|
||||
interceptor func(method string, rq *http.Request)
|
||||
c *http.Client
|
||||
|
||||
authMutex sync.Mutex
|
||||
auth Authenticator
|
||||
}
|
||||
|
||||
// Authenticator stub
|
||||
type Authenticator interface {
|
||||
Type() string
|
||||
User() string
|
||||
Pass() string
|
||||
Authorize(*http.Request, string, string)
|
||||
}
|
||||
|
||||
// NoAuth structure holds our credentials
|
||||
type NoAuth struct {
|
||||
user string
|
||||
pw string
|
||||
}
|
||||
|
||||
// Type identifies the authenticator
|
||||
func (n *NoAuth) Type() string {
|
||||
return "NoAuth"
|
||||
}
|
||||
|
||||
// User returns the current user
|
||||
func (n *NoAuth) User() string {
|
||||
return n.user
|
||||
}
|
||||
|
||||
// Pass returns the current password
|
||||
func (n *NoAuth) Pass() string {
|
||||
return n.pw
|
||||
}
|
||||
|
||||
// Authorize the current request
|
||||
func (n *NoAuth) Authorize(req *http.Request, method string, path string) {
|
||||
auth Authorizer
|
||||
}
|
||||
|
||||
// NewClient creates a new instance of client
|
||||
func NewClient(uri, user, pw string) *Client {
|
||||
return &Client{FixSlash(uri), make(http.Header), nil, &http.Client{}, sync.Mutex{}, &NoAuth{user, pw}}
|
||||
return NewAuthClient(uri, NewAutoAuth(user, pw))
|
||||
}
|
||||
|
||||
// NewAuthClient creates a new client instance with a custom Authorizer
|
||||
func NewAuthClient(uri string, auth Authorizer) *Client {
|
||||
c := &http.Client{
|
||||
CheckRedirect: func(rq *http.Request, via []*http.Request) error {
|
||||
if len(via) >= 10 {
|
||||
return ErrTooManyRedirects
|
||||
}
|
||||
if via[0].Header.Get(XInhibitRedirect) != "" {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return &Client{FixSlash(uri), make(http.Header), nil, c, auth}
|
||||
}
|
||||
|
||||
// SetHeader lets us set arbitrary headers for a given client
|
||||
@@ -101,7 +83,7 @@ func (c *Client) Connect() error {
|
||||
}
|
||||
|
||||
if rs.StatusCode != 200 {
|
||||
return newPathError("Connect", c.root, rs.StatusCode)
|
||||
return NewPathError("Connect", c.root, rs.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -145,7 +127,7 @@ func (c *Client) ReadDir(path string) ([]os.FileInfo, error) {
|
||||
r.Props = nil
|
||||
return nil
|
||||
}
|
||||
return newPathError("ReadDir", path, 405)
|
||||
return NewPathError("ReadDir", path, 405)
|
||||
}
|
||||
|
||||
if p := getProps(r, "200"); p != nil {
|
||||
@@ -192,7 +174,7 @@ func (c *Client) ReadDir(path string) ([]os.FileInfo, error) {
|
||||
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
err = newPathErrorErr("ReadDir", path, err)
|
||||
err = NewPathErrorErr("ReadDir", path, err)
|
||||
}
|
||||
}
|
||||
return files, err
|
||||
@@ -244,7 +226,7 @@ func (c *Client) Stat(path string) (os.FileInfo, error) {
|
||||
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
err = newPathErrorErr("ReadDir", path, err)
|
||||
err = NewPathErrorErr("ReadDir", path, err)
|
||||
}
|
||||
}
|
||||
return f, err
|
||||
@@ -259,7 +241,7 @@ func (c *Client) Remove(path string) error {
|
||||
func (c *Client) RemoveAll(path string) error {
|
||||
rs, err := c.req("DELETE", path, nil, nil)
|
||||
if err != nil {
|
||||
return newPathError("Remove", path, 400)
|
||||
return NewPathError("Remove", path, 400)
|
||||
}
|
||||
err = rs.Body.Close()
|
||||
if err != nil {
|
||||
@@ -270,7 +252,7 @@ func (c *Client) RemoveAll(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return newPathError("Remove", path, rs.StatusCode)
|
||||
return NewPathError("Remove", path, rs.StatusCode)
|
||||
}
|
||||
|
||||
// Mkdir makes a directory
|
||||
@@ -284,7 +266,7 @@ func (c *Client) Mkdir(path string, _ os.FileMode) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return newPathError("Mkdir", path, status)
|
||||
return NewPathError("Mkdir", path, status)
|
||||
}
|
||||
|
||||
// MkdirAll like mkdir -p, but for webdav
|
||||
@@ -310,13 +292,13 @@ func (c *Client) MkdirAll(path string, _ os.FileMode) (err error) {
|
||||
return
|
||||
}
|
||||
if status != 201 {
|
||||
return newPathError("MkdirAll", sub, status)
|
||||
return NewPathError("MkdirAll", sub, status)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return newPathError("MkdirAll", path, status)
|
||||
return NewPathError("MkdirAll", path, status)
|
||||
}
|
||||
|
||||
// Rename moves a file from A to B
|
||||
@@ -351,7 +333,7 @@ func (c *Client) Read(path string) ([]byte, error) {
|
||||
func (c *Client) ReadStream(path string) (io.ReadCloser, error) {
|
||||
rs, err := c.req("GET", path, nil, nil)
|
||||
if err != nil {
|
||||
return nil, newPathErrorErr("ReadStream", path, err)
|
||||
return nil, NewPathErrorErr("ReadStream", path, err)
|
||||
}
|
||||
|
||||
if rs.StatusCode == 200 {
|
||||
@@ -359,7 +341,7 @@ func (c *Client) ReadStream(path string) (io.ReadCloser, error) {
|
||||
}
|
||||
|
||||
rs.Body.Close()
|
||||
return nil, newPathError("ReadStream", path, rs.StatusCode)
|
||||
return nil, NewPathError("ReadStream", path, rs.StatusCode)
|
||||
}
|
||||
|
||||
// ReadStreamRange reads the stream representing a subset of bytes for a given path,
|
||||
@@ -379,7 +361,7 @@ func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadClos
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, newPathErrorErr("ReadStreamRange", path, err)
|
||||
return nil, NewPathErrorErr("ReadStreamRange", path, err)
|
||||
}
|
||||
|
||||
if rs.StatusCode == http.StatusPartialContent {
|
||||
@@ -392,7 +374,7 @@ func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadClos
|
||||
if rs.StatusCode == 200 {
|
||||
// discard first 'offset' bytes.
|
||||
if _, err := io.Copy(io.Discard, io.LimitReader(rs.Body, offset)); err != nil {
|
||||
return nil, newPathErrorErr("ReadStreamRange", path, err)
|
||||
return nil, NewPathErrorErr("ReadStreamRange", path, err)
|
||||
}
|
||||
|
||||
// return a io.ReadCloser that is limited to `length` bytes.
|
||||
@@ -400,7 +382,7 @@ func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadClos
|
||||
}
|
||||
|
||||
rs.Body.Close()
|
||||
return nil, newPathError("ReadStream", path, rs.StatusCode)
|
||||
return nil, NewPathError("ReadStream", path, rs.StatusCode)
|
||||
}
|
||||
|
||||
// Write writes data to a given path
|
||||
@@ -430,7 +412,7 @@ func (c *Client) Write(path string, data []byte, _ os.FileMode) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
return newPathError("Write", path, s)
|
||||
return NewPathError("Write", path, s)
|
||||
}
|
||||
|
||||
// WriteStream writes a stream
|
||||
@@ -451,6 +433,6 @@ func (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err
|
||||
return nil
|
||||
|
||||
default:
|
||||
return newPathError("WriteStream", path, s)
|
||||
return NewPathError("WriteStream", path, s)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user