oauthutil: support backend-specific errorHandler

This allows the backend to pass a errorHandler function to the doConfig
function. The webserver will pass the current request as a parameter to
the function.
The function can then examine all paramters and build the AuthError
struct which contains name, code, description of the error. A link to
the docs can be added to the HelpURL field.
oauthutil then takes care of formatting for the HTML response page. The
error details are also returned as an error in the server.err channel
and will be logged to the commandline.
This commit is contained in:
Henning Surmeier 2018-06-01 12:34:35 +02:00 committed by Nick Craig-Wood
parent ba43acb6aa
commit c5286ee157

View file

@ -262,16 +262,22 @@ func NewClient(name string, oauthConfig *oauth2.Config) (*http.Client, *TokenSou
//
// It may run an internal webserver to receive the results
func Config(id, name string, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error {
return doConfig(id, name, config, true, opts)
return doConfig(id, name, nil, config, true, opts)
}
// ConfigNoOffline does the same as Config but does not pass the
// "access_type=offline" parameter.
func ConfigNoOffline(id, name string, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error {
return doConfig(id, name, config, false, opts)
return doConfig(id, name, nil, config, false, opts)
}
func doConfig(id, name string, oauthConfig *oauth2.Config, offline bool, opts []oauth2.AuthCodeOption) error {
// ConfigErrorCheck does the same as Config, but allows the backend to pass a error handling function
// This function gets called with the request made to rclone as a parameter if no code was found
func ConfigErrorCheck(id, name string, errorHandler func(*http.Request) AuthError, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error {
return doConfig(id, name, errorHandler, config, true, opts)
}
func doConfig(id, name string, errorHandler func(*http.Request) AuthError, oauthConfig *oauth2.Config, offline bool, opts []oauth2.AuthCodeOption) error {
oauthConfig, changed := overrideCredentials(name, oauthConfig)
automatic := config.FileGet(name, config.ConfigAutomatic) != ""
@ -350,12 +356,14 @@ func doConfig(id, name string, oauthConfig *oauth2.Config, offline bool, opts []
// Prepare webserver
server := authServer{
state: state,
bindAddress: bindAddress,
authURL: authURL,
state: state,
bindAddress: bindAddress,
authURL: authURL,
errorHandler: errorHandler,
}
if useWebServer {
server.code = make(chan string, 1)
server.err = make(chan error, 1)
go server.Start()
defer server.Stop()
authURL = "http://" + bindAddress + "/auth"
@ -371,9 +379,13 @@ func doConfig(id, name string, oauthConfig *oauth2.Config, offline bool, opts []
// Read the code, and exchange it for a token.
fmt.Printf("Waiting for code...\n")
authCode = <-server.code
authError := <-server.err
if authCode != "" {
fmt.Printf("Got code\n")
} else {
if authError != nil {
return authError
}
return errors.New("failed to get code")
}
} else {
@ -399,12 +411,22 @@ func doConfig(id, name string, oauthConfig *oauth2.Config, offline bool, opts []
// Local web server for collecting auth
type authServer struct {
state string
listener net.Listener
bindAddress string
code chan string
authURL string
server *http.Server
state string
listener net.Listener
bindAddress string
code chan string
err chan error
authURL string
server *http.Server
errorHandler func(*http.Request) AuthError
}
// AuthError gets returned by the backend's errorHandler function
type AuthError struct {
Name string
Description string
Code string
HelpURL string
}
// startWebServer runs an internal web server to receive config details
@ -428,6 +450,7 @@ func (s *authServer) Start() {
w.Header().Set("Content-Type", "text/html")
fs.Debugf(nil, "Received request on auth server")
code := req.FormValue("code")
var err error
if code != "" {
state := req.FormValue("state")
if state != s.state {
@ -443,11 +466,20 @@ func (s *authServer) Start() {
}
} else {
fs.Debugf(nil, "No code found on request")
httpResponse := "<h1>Failed!</h1>\nNo code found returned by remote server."
if s.errorHandler != nil {
authError := s.errorHandler(req)
errorDesc := fmt.Sprintf("Error: %s\nCode: %s\nDescription: %s\nHelp: %s",
authError.Name, authError.Code, authError.Description, authError.HelpURL)
httpResponse += "\n<p>" + strings.Replace(errorDesc, "\n", "<br>", -1) + "</p>"
err = errors.New(errorDesc)
}
w.WriteHeader(500)
_, _ = fmt.Fprintf(w, "<h1>Failed!</h1>\nNo code found returned by remote server.")
_, _ = fmt.Fprintf(w, httpResponse)
}
if s.code != nil {
s.code <- code
s.err <- err
}
})