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 // It may run an internal webserver to receive the results
func Config(id, name string, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error { 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 // ConfigNoOffline does the same as Config but does not pass the
// "access_type=offline" parameter. // "access_type=offline" parameter.
func ConfigNoOffline(id, name string, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error { 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) oauthConfig, changed := overrideCredentials(name, oauthConfig)
automatic := config.FileGet(name, config.ConfigAutomatic) != "" automatic := config.FileGet(name, config.ConfigAutomatic) != ""
@ -350,12 +356,14 @@ func doConfig(id, name string, oauthConfig *oauth2.Config, offline bool, opts []
// Prepare webserver // Prepare webserver
server := authServer{ server := authServer{
state: state, state: state,
bindAddress: bindAddress, bindAddress: bindAddress,
authURL: authURL, authURL: authURL,
errorHandler: errorHandler,
} }
if useWebServer { if useWebServer {
server.code = make(chan string, 1) server.code = make(chan string, 1)
server.err = make(chan error, 1)
go server.Start() go server.Start()
defer server.Stop() defer server.Stop()
authURL = "http://" + bindAddress + "/auth" 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. // Read the code, and exchange it for a token.
fmt.Printf("Waiting for code...\n") fmt.Printf("Waiting for code...\n")
authCode = <-server.code authCode = <-server.code
authError := <-server.err
if authCode != "" { if authCode != "" {
fmt.Printf("Got code\n") fmt.Printf("Got code\n")
} else { } else {
if authError != nil {
return authError
}
return errors.New("failed to get code") return errors.New("failed to get code")
} }
} else { } else {
@ -399,12 +411,22 @@ func doConfig(id, name string, oauthConfig *oauth2.Config, offline bool, opts []
// Local web server for collecting auth // Local web server for collecting auth
type authServer struct { type authServer struct {
state string state string
listener net.Listener listener net.Listener
bindAddress string bindAddress string
code chan string code chan string
authURL string err chan error
server *http.Server 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 // 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") w.Header().Set("Content-Type", "text/html")
fs.Debugf(nil, "Received request on auth server") fs.Debugf(nil, "Received request on auth server")
code := req.FormValue("code") code := req.FormValue("code")
var err error
if code != "" { if code != "" {
state := req.FormValue("state") state := req.FormValue("state")
if state != s.state { if state != s.state {
@ -443,11 +466,20 @@ func (s *authServer) Start() {
} }
} else { } else {
fs.Debugf(nil, "No code found on request") 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) w.WriteHeader(500)
_, _ = fmt.Fprintf(w, "<h1>Failed!</h1>\nNo code found returned by remote server.") _, _ = fmt.Fprintf(w, httpResponse)
} }
if s.code != nil { if s.code != nil {
s.code <- code s.code <- code
s.err <- err
} }
}) })