Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Nick Craig-Wood
bbb31d6acf lib/oauthutil: allow the browser opening function to be overridden 2024-10-24 13:12:11 +01:00
Nick Craig-Wood
7c705e0efa oauthutil: shut down the oauth webserver when the context closes
This patch ensures that we pass the context from the CreateBackend
call all the way to the creation of the oauth web server.

This means that when the command has finished the webserver will
definitely be shut down, and if the user abandons it (eg via an rc
call timing out or being cancelled) then it will be shut down too.
2024-10-24 12:51:55 +01:00

View file

@ -80,6 +80,11 @@ All done. Please go back to rclone.
` `
) )
// OpenURL is used when rclone wants to open a browser window
// for user authentication. It defaults to something which
// should work for most uses, but may be overridden.
var OpenURL = open.Start
// SharedOptions are shared between backends the utilize an OAuth flow // SharedOptions are shared between backends the utilize an OAuth flow
var SharedOptions = []fs.Option{{ var SharedOptions = []fs.Option{{
Name: config.ConfigClientID, Name: config.ConfigClientID,
@ -706,7 +711,7 @@ func configSetup(ctx context.Context, id, name string, m configmap.Mapper, oauth
// Prepare webserver // Prepare webserver
server := newAuthServer(opt, bindAddress, state, authURL) server := newAuthServer(opt, bindAddress, state, authURL)
err = server.Init() err = server.Init(ctx)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to start auth webserver: %w", err) return "", fmt.Errorf("failed to start auth webserver: %w", err)
} }
@ -716,8 +721,12 @@ func configSetup(ctx context.Context, id, name string, m configmap.Mapper, oauth
if !authorizeNoAutoBrowser { if !authorizeNoAutoBrowser {
// Open the URL for the user to visit // Open the URL for the user to visit
_ = open.Start(authURL) err := OpenURL(authURL)
if err != nil {
fs.Errorf(nil, "Failed to open browser automatically (%v) - please go to the following link: %s\n", err, authURL)
} else {
fs.Logf(nil, "If your browser doesn't open automatically go to the following link: %s\n", authURL) fs.Logf(nil, "If your browser doesn't open automatically go to the following link: %s\n", authURL)
}
} else { } else {
fs.Logf(nil, "Please go to the following link: %s\n", authURL) fs.Logf(nil, "Please go to the following link: %s\n", authURL)
} }
@ -758,6 +767,7 @@ type authServer struct {
authURL string authURL string
server *http.Server server *http.Server
result chan *AuthResult result chan *AuthResult
quit chan struct{}
} }
// newAuthServer makes the webserver for collecting auth // newAuthServer makes the webserver for collecting auth
@ -768,6 +778,7 @@ func newAuthServer(opt *Options, bindAddress, state, authURL string) *authServer
bindAddress: bindAddress, bindAddress: bindAddress,
authURL: authURL, // http://host/auth redirects to here authURL: authURL, // http://host/auth redirects to here
result: make(chan *AuthResult, 1), result: make(chan *AuthResult, 1),
quit: make(chan struct{}),
} }
} }
@ -830,15 +841,32 @@ func (s *authServer) handleAuth(w http.ResponseWriter, req *http.Request) {
} }
// Init gets the internal web server ready to receive config details // Init gets the internal web server ready to receive config details
func (s *authServer) Init() error { //
// The web server will listen until ctx is cancelled or the Stop()
// method is called
func (s *authServer) Init(ctx context.Context) error {
fs.Debugf(nil, "Starting auth server on %s", s.bindAddress) fs.Debugf(nil, "Starting auth server on %s", s.bindAddress)
mux := http.NewServeMux() mux := http.NewServeMux()
s.server = &http.Server{ s.server = &http.Server{
Addr: s.bindAddress, Addr: s.bindAddress,
Handler: mux, Handler: mux,
BaseContext: func(net.Listener) context.Context { return ctx },
} }
s.server.SetKeepAlivesEnabled(false) s.server.SetKeepAlivesEnabled(false)
// Error the server if the context is cancelled
go func() {
select {
case <-ctx.Done():
s.result <- &AuthResult{
Name: "Cancelled",
Description: ctx.Err().Error(),
Err: ctx.Err(),
}
case <-s.quit:
}
}()
mux.HandleFunc("/auth", func(w http.ResponseWriter, req *http.Request) { mux.HandleFunc("/auth", func(w http.ResponseWriter, req *http.Request) {
state := req.FormValue("state") state := req.FormValue("state")
if state != s.state { if state != s.state {
@ -852,7 +880,8 @@ func (s *authServer) Init() error {
mux.HandleFunc("/", s.handleAuth) mux.HandleFunc("/", s.handleAuth)
var err error var err error
s.listener, err = net.Listen("tcp", s.bindAddress) var lc net.ListenConfig
s.listener, err = lc.Listen(ctx, "tcp", s.bindAddress)
if err != nil { if err != nil {
return err return err
} }
@ -869,6 +898,7 @@ func (s *authServer) Serve() {
func (s *authServer) Stop() { func (s *authServer) Stop() {
fs.Debugf(nil, "Closing auth server") fs.Debugf(nil, "Closing auth server")
close(s.result) close(s.result)
close(s.quit)
_ = s.listener.Close() _ = s.listener.Close()
// close the server // close the server