github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/lib/oauthutil/oauthutil.go (about) 1 package oauthutil 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "html/template" 8 "net" 9 "net/http" 10 "net/url" 11 "sync" 12 "time" 13 14 "github.com/pkg/errors" 15 "github.com/rclone/rclone/fs" 16 "github.com/rclone/rclone/fs/config" 17 "github.com/rclone/rclone/fs/config/configmap" 18 "github.com/rclone/rclone/fs/fshttp" 19 "github.com/rclone/rclone/lib/random" 20 "github.com/skratchdot/open-golang/open" 21 "golang.org/x/oauth2" 22 ) 23 24 const ( 25 // TitleBarRedirectURL is the OAuth2 redirect URL to use when the authorization 26 // code should be returned in the title bar of the browser, with the page text 27 // prompting the user to copy the code and paste it in the application. 28 TitleBarRedirectURL = "urn:ietf:wg:oauth:2.0:oob" 29 30 // bindPort is the port that we bind the local webserver to 31 bindPort = "53682" 32 33 // bindAddress is binding for local webserver when active 34 bindAddress = "127.0.0.1:" + bindPort 35 36 // RedirectURL is redirect to local webserver when active 37 RedirectURL = "http://" + bindAddress + "/" 38 39 // RedirectPublicURL is redirect to local webserver when active with public name 40 RedirectPublicURL = "http://localhost.rclone.org:" + bindPort + "/" 41 42 // RedirectLocalhostURL is redirect to local webserver when active with localhost 43 RedirectLocalhostURL = "http://localhost:" + bindPort + "/" 44 45 // RedirectPublicSecureURL is a public https URL which 46 // redirects to the local webserver 47 RedirectPublicSecureURL = "https://oauth.rclone.org/" 48 49 // AuthResponseTemplate is a template to handle the redirect URL for oauth requests 50 AuthResponseTemplate = `<!DOCTYPE html> 51 <html lang="en"> 52 <head> 53 <meta charset="utf-8"> 54 <title>{{ if .OK }}Success!{{ else }}Failure!{{ end }}</title> 55 </head> 56 <body> 57 <h1>{{ if .OK }}Success!{{ else }}Failure!{{ end }}</h1> 58 <hr> 59 <pre style="width: 750px; white-space: pre-wrap;"> 60 {{ if eq .OK false }} 61 Error: {{ .Name }}<br> 62 {{ if .Description }}Description: {{ .Description }}<br>{{ end }} 63 {{ if .Code }}Code: {{ .Code }}<br>{{ end }} 64 {{ if .HelpURL }}Look here for help: <a href="{{ .HelpURL }}">{{ .HelpURL }}</a><br>{{ end }} 65 {{ else }} 66 All done. Please go back to rclone. 67 {{ end }} 68 </pre> 69 </body> 70 </html> 71 ` 72 ) 73 74 // oldToken contains an end-user's tokens. 75 // This is the data you must store to persist authentication. 76 // 77 // From the original code.google.com/p/goauth2/oauth package - used 78 // for backwards compatibility in the rclone config file 79 type oldToken struct { 80 AccessToken string 81 RefreshToken string 82 Expiry time.Time 83 } 84 85 // GetToken returns the token saved in the config file under 86 // section name. 87 func GetToken(name string, m configmap.Mapper) (*oauth2.Token, error) { 88 tokenString, ok := m.Get(config.ConfigToken) 89 if !ok || tokenString == "" { 90 return nil, errors.Errorf("empty token found - please run \"rclone config reconnect %s:\"", name) 91 } 92 token := new(oauth2.Token) 93 err := json.Unmarshal([]byte(tokenString), token) 94 if err != nil { 95 return nil, err 96 } 97 // if has data then return it 98 if token.AccessToken != "" { 99 return token, nil 100 } 101 // otherwise try parsing as oldToken 102 oldtoken := new(oldToken) 103 err = json.Unmarshal([]byte(tokenString), oldtoken) 104 if err != nil { 105 return nil, err 106 } 107 // Fill in result into new token 108 token.AccessToken = oldtoken.AccessToken 109 token.RefreshToken = oldtoken.RefreshToken 110 token.Expiry = oldtoken.Expiry 111 // Save new format in config file 112 err = PutToken(name, m, token, false) 113 if err != nil { 114 return nil, err 115 } 116 return token, nil 117 } 118 119 // PutToken stores the token in the config file 120 // 121 // This saves the config file if it changes 122 func PutToken(name string, m configmap.Mapper, token *oauth2.Token, newSection bool) error { 123 tokenBytes, err := json.Marshal(token) 124 if err != nil { 125 return err 126 } 127 tokenString := string(tokenBytes) 128 old, ok := m.Get(config.ConfigToken) 129 if !ok || tokenString != old { 130 err = config.SetValueAndSave(name, config.ConfigToken, tokenString) 131 if newSection && err != nil { 132 fs.Debugf(name, "Added new token to config, still needs to be saved") 133 } else if err != nil { 134 fs.Errorf(nil, "Failed to save new token in config file: %v", err) 135 } else { 136 fs.Debugf(name, "Saved new token in config file") 137 } 138 } 139 return nil 140 } 141 142 // TokenSource stores updated tokens in the config file 143 type TokenSource struct { 144 mu sync.Mutex 145 name string 146 m configmap.Mapper 147 tokenSource oauth2.TokenSource 148 token *oauth2.Token 149 config *oauth2.Config 150 ctx context.Context 151 expiryTimer *time.Timer // signals whenever the token expires 152 } 153 154 // If token has expired then first try re-reading it from the config 155 // file in case a concurrently running rclone has updated it already 156 func (ts *TokenSource) reReadToken() bool { 157 tokenString, err := config.FileGetFresh(ts.name, config.ConfigToken) 158 if err != nil { 159 fs.Debugf(ts.name, "Failed to read token out of config file: %v", err) 160 return false 161 } 162 newToken := new(oauth2.Token) 163 err = json.Unmarshal([]byte(tokenString), newToken) 164 if err != nil { 165 fs.Debugf(ts.name, "Failed to parse token out of config file: %v", err) 166 return false 167 } 168 if !newToken.Valid() { 169 fs.Debugf(ts.name, "Loaded invalid token from config file - ignoring") 170 return false 171 } 172 fs.Debugf(ts.name, "Loaded fresh token from config file") 173 ts.token = newToken 174 ts.tokenSource = nil // invalidate since we changed the token 175 return true 176 } 177 178 // Token returns a token or an error. 179 // Token must be safe for concurrent use by multiple goroutines. 180 // The returned Token must not be modified. 181 // 182 // This saves the token in the config file if it has changed 183 func (ts *TokenSource) Token() (*oauth2.Token, error) { 184 ts.mu.Lock() 185 defer ts.mu.Unlock() 186 var ( 187 token *oauth2.Token 188 err error 189 changed = false 190 ) 191 const maxTries = 5 192 193 // Try getting the token a few times 194 for i := 1; i <= maxTries; i++ { 195 // Try reading the token from the config file in case it has 196 // been updated by a concurrent rclone process 197 if !ts.token.Valid() { 198 if ts.reReadToken() { 199 changed = true 200 } 201 } 202 203 // Make a new token source if required 204 if ts.tokenSource == nil { 205 ts.tokenSource = ts.config.TokenSource(ts.ctx, ts.token) 206 } 207 208 token, err = ts.tokenSource.Token() 209 if err == nil { 210 break 211 } 212 fs.Debugf(ts.name, "Token refresh failed try %d/%d: %v", i, maxTries, err) 213 time.Sleep(1 * time.Second) 214 } 215 if err != nil { 216 return nil, errors.Wrapf(err, "couldn't fetch token - maybe it has expired? - refresh with \"rclone config reconnect %s:\"", ts.name) 217 } 218 changed = changed || (*token != *ts.token) 219 ts.token = token 220 if changed { 221 // Bump on the expiry timer if it is set 222 if ts.expiryTimer != nil { 223 ts.expiryTimer.Reset(ts.timeToExpiry()) 224 } 225 err = PutToken(ts.name, ts.m, token, false) 226 if err != nil { 227 return nil, errors.Wrap(err, "couldn't store token") 228 } 229 } 230 return token, nil 231 } 232 233 // Invalidate invalidates the token 234 func (ts *TokenSource) Invalidate() { 235 ts.mu.Lock() 236 ts.token.AccessToken = "" 237 ts.mu.Unlock() 238 } 239 240 // timeToExpiry returns how long until the token expires 241 // 242 // Call with the lock held 243 func (ts *TokenSource) timeToExpiry() time.Duration { 244 t := ts.token 245 if t == nil { 246 return 0 247 } 248 if t.Expiry.IsZero() { 249 return 3e9 * time.Second // ~95 years 250 } 251 return t.Expiry.Sub(time.Now()) 252 } 253 254 // OnExpiry returns a channel which has the time written to it when 255 // the token expires. Note that there is only one channel so if 256 // attaching multiple go routines it will only signal to one of them. 257 func (ts *TokenSource) OnExpiry() <-chan time.Time { 258 ts.mu.Lock() 259 defer ts.mu.Unlock() 260 if ts.expiryTimer == nil { 261 ts.expiryTimer = time.NewTimer(ts.timeToExpiry()) 262 } 263 return ts.expiryTimer.C 264 } 265 266 // Check interface satisfied 267 var _ oauth2.TokenSource = (*TokenSource)(nil) 268 269 // Context returns a context with our HTTP Client baked in for oauth2 270 func Context(client *http.Client) context.Context { 271 return context.WithValue(context.Background(), oauth2.HTTPClient, client) 272 } 273 274 // overrideCredentials sets the ClientID and ClientSecret from the 275 // config file if they are not blank. 276 // If any value is overridden, true is returned. 277 // the origConfig is copied 278 func overrideCredentials(name string, m configmap.Mapper, origConfig *oauth2.Config) (newConfig *oauth2.Config, changed bool) { 279 newConfig = new(oauth2.Config) 280 *newConfig = *origConfig 281 changed = false 282 ClientID, ok := m.Get(config.ConfigClientID) 283 if ok && ClientID != "" { 284 newConfig.ClientID = ClientID 285 changed = true 286 } 287 ClientSecret, ok := m.Get(config.ConfigClientSecret) 288 if ok && ClientSecret != "" { 289 newConfig.ClientSecret = ClientSecret 290 changed = true 291 } 292 AuthURL, ok := m.Get(config.ConfigAuthURL) 293 if ok && AuthURL != "" { 294 newConfig.Endpoint.AuthURL = AuthURL 295 changed = true 296 } 297 TokenURL, ok := m.Get(config.ConfigTokenURL) 298 if ok && TokenURL != "" { 299 newConfig.Endpoint.TokenURL = TokenURL 300 changed = true 301 } 302 return newConfig, changed 303 } 304 305 // NewClientWithBaseClient gets a token from the config file and 306 // configures a Client with it. It returns the client and a 307 // TokenSource which Invalidate may need to be called on. It uses the 308 // httpClient passed in as the base client. 309 func NewClientWithBaseClient(name string, m configmap.Mapper, config *oauth2.Config, baseClient *http.Client) (*http.Client, *TokenSource, error) { 310 config, _ = overrideCredentials(name, m, config) 311 token, err := GetToken(name, m) 312 if err != nil { 313 return nil, nil, err 314 } 315 316 // Set our own http client in the context 317 ctx := Context(baseClient) 318 319 // Wrap the TokenSource in our TokenSource which saves changed 320 // tokens in the config file 321 ts := &TokenSource{ 322 name: name, 323 m: m, 324 token: token, 325 config: config, 326 ctx: ctx, 327 } 328 return oauth2.NewClient(ctx, ts), ts, nil 329 330 } 331 332 // NewClient gets a token from the config file and configures a Client 333 // with it. It returns the client and a TokenSource which Invalidate may need to be called on 334 func NewClient(name string, m configmap.Mapper, oauthConfig *oauth2.Config) (*http.Client, *TokenSource, error) { 335 return NewClientWithBaseClient(name, m, oauthConfig, fshttp.NewClient(fs.Config)) 336 } 337 338 // AuthResult is returned from the web server after authorization 339 // success or failure 340 type AuthResult struct { 341 OK bool // Failure or Success? 342 Name string 343 Description string 344 Code string 345 HelpURL string 346 Form url.Values // the complete contents of the form 347 Err error // any underlying error to report 348 } 349 350 // Error satisfies the error interface so AuthResult can be used as an error 351 func (ar *AuthResult) Error() string { 352 status := "Error" 353 if ar.OK { 354 status = "OK" 355 } 356 return fmt.Sprintf("%s: %s\nCode: %q\nDescription: %s\nHelp: %s", 357 status, ar.Name, ar.Code, ar.Description, ar.HelpURL) 358 } 359 360 // CheckAuthFn is called when a good Auth has been received 361 type CheckAuthFn func(*oauth2.Config, *AuthResult) error 362 363 // Options for the oauth config 364 type Options struct { 365 NoOffline bool // If set then "access_type=offline" parameter is not passed 366 CheckAuth CheckAuthFn // When the AuthResult is known the checkAuth function is called if set 367 OAuth2Opts []oauth2.AuthCodeOption // extra oauth2 options 368 StateBlankOK bool // If set, state returned as "" is deemed to be OK 369 } 370 371 // Config does the initial creation of the token 372 // 373 // If opt is nil it will use the default Options 374 // 375 // It may run an internal webserver to receive the results 376 func Config(id, name string, m configmap.Mapper, oauthConfig *oauth2.Config, opt *Options) error { 377 if opt == nil { 378 opt = &Options{} 379 } 380 oauthConfig, changed := overrideCredentials(name, m, oauthConfig) 381 authorizeOnlyValue, ok := m.Get(config.ConfigAuthorize) 382 authorizeOnly := ok && authorizeOnlyValue != "" // set if being run by "rclone authorize" 383 authorizeNoAutoBrowserValue, ok := m.Get(config.ConfigAuthNoBrowser) 384 authorizeNoAutoBrowser := ok && authorizeNoAutoBrowserValue != "" 385 386 // See if already have a token 387 tokenString, ok := m.Get("token") 388 if ok && tokenString != "" { 389 fmt.Printf("Already have a token - refresh?\n") 390 if !config.ConfirmWithConfig(m, "config_refresh_token", true) { 391 return nil 392 } 393 } 394 395 // Ask the user whether they are using a local machine 396 isLocal := func() bool { 397 fmt.Printf("Use auto config?\n") 398 fmt.Printf(" * Say Y if not sure\n") 399 fmt.Printf(" * Say N if you are working on a remote or headless machine\n") 400 return config.ConfirmWithConfig(m, "config_is_local", true) 401 } 402 403 // Detect whether we should use internal web server 404 useWebServer := false 405 switch oauthConfig.RedirectURL { 406 case TitleBarRedirectURL: 407 useWebServer = authorizeOnly 408 if !authorizeOnly { 409 useWebServer = isLocal() 410 } 411 if useWebServer { 412 // copy the config and set to use the internal webserver 413 configCopy := *oauthConfig 414 oauthConfig = &configCopy 415 oauthConfig.RedirectURL = RedirectURL 416 } 417 default: 418 if changed { 419 fmt.Printf("Make sure your Redirect URL is set to %q in your custom config.\n", oauthConfig.RedirectURL) 420 } 421 useWebServer = true 422 if authorizeOnly { 423 break 424 } 425 if !isLocal() { 426 fmt.Printf(`For this to work, you will need rclone available on a machine that has 427 a web browser available. 428 429 For more help and alternate methods see: https://rclone.org/remote_setup/ 430 431 Execute the following on the machine with the web browser (same rclone 432 version recommended): 433 434 `) 435 if changed { 436 fmt.Printf("\trclone authorize %q -- %q %q\n", id, oauthConfig.ClientID, oauthConfig.ClientSecret) 437 } else { 438 fmt.Printf("\trclone authorize %q\n", id) 439 } 440 fmt.Println("\nThen paste the result below:") 441 code := config.ReadNonEmptyLine("result> ") 442 token := &oauth2.Token{} 443 err := json.Unmarshal([]byte(code), token) 444 if err != nil { 445 return err 446 } 447 return PutToken(name, m, token, true) 448 } 449 } 450 451 // Make random state 452 state, err := random.Password(128) 453 if err != nil { 454 return err 455 } 456 457 // Generate oauth URL 458 opts := opt.OAuth2Opts 459 if !opt.NoOffline { 460 opts = append(opts, oauth2.AccessTypeOffline) 461 } 462 authURL := oauthConfig.AuthCodeURL(state, opts...) 463 464 // Prepare webserver if needed 465 var server *authServer 466 if useWebServer { 467 server = newAuthServer(opt, bindAddress, state, authURL) 468 err := server.Init() 469 if err != nil { 470 return errors.Wrap(err, "failed to start auth webserver") 471 } 472 go server.Serve() 473 defer server.Stop() 474 authURL = "http://" + bindAddress + "/auth?state=" + state 475 } 476 477 if !authorizeNoAutoBrowser && oauthConfig.RedirectURL != TitleBarRedirectURL { 478 // Open the URL for the user to visit 479 _ = open.Start(authURL) 480 fmt.Printf("If your browser doesn't open automatically go to the following link: %s\n", authURL) 481 } else { 482 fmt.Printf("Please go to the following link: %s\n", authURL) 483 } 484 fmt.Printf("Log in and authorize rclone for access\n") 485 486 // Read the code via the webserver or manually 487 var auth *AuthResult 488 if useWebServer { 489 fmt.Printf("Waiting for code...\n") 490 auth = <-server.result 491 if !auth.OK || auth.Code == "" { 492 return auth 493 } 494 fmt.Printf("Got code\n") 495 if opt.CheckAuth != nil { 496 err = opt.CheckAuth(oauthConfig, auth) 497 if err != nil { 498 return err 499 } 500 } 501 } else { 502 auth = &AuthResult{ 503 Code: config.ReadNonEmptyLine("Enter verification code> "), 504 } 505 } 506 507 // Exchange the code for a token 508 ctx := Context(fshttp.NewClient(fs.Config)) 509 token, err := oauthConfig.Exchange(ctx, auth.Code) 510 if err != nil { 511 return errors.Wrap(err, "failed to get token") 512 } 513 514 // Print code if we are doing a manual auth 515 if authorizeOnly { 516 result, err := json.Marshal(token) 517 if err != nil { 518 return errors.Wrap(err, "failed to marshal token") 519 } 520 fmt.Printf("Paste the following into your remote machine --->\n%s\n<---End paste\n", result) 521 } 522 return PutToken(name, m, token, true) 523 } 524 525 // Local web server for collecting auth 526 type authServer struct { 527 opt *Options 528 state string 529 listener net.Listener 530 bindAddress string 531 authURL string 532 server *http.Server 533 result chan *AuthResult 534 } 535 536 // newAuthServer makes the webserver for collecting auth 537 func newAuthServer(opt *Options, bindAddress, state, authURL string) *authServer { 538 return &authServer{ 539 opt: opt, 540 state: state, 541 bindAddress: bindAddress, 542 authURL: authURL, // http://host/auth redirects to here 543 result: make(chan *AuthResult, 1), 544 } 545 } 546 547 // Receive the auth request 548 func (s *authServer) handleAuth(w http.ResponseWriter, req *http.Request) { 549 fs.Debugf(nil, "Received %s request on auth server to %q", req.Method, req.URL.Path) 550 551 // Reply with the response to the user and to the channel 552 reply := func(status int, res *AuthResult) { 553 w.WriteHeader(status) 554 w.Header().Set("Content-Type", "text/html") 555 var t = template.Must(template.New("authResponse").Parse(AuthResponseTemplate)) 556 if err := t.Execute(w, res); err != nil { 557 fs.Debugf(nil, "Could not execute template for web response.") 558 } 559 s.result <- res 560 } 561 562 // Parse the form parameters and save them 563 err := req.ParseForm() 564 if err != nil { 565 reply(http.StatusBadRequest, &AuthResult{ 566 Name: "Parse form error", 567 Description: err.Error(), 568 }) 569 return 570 } 571 572 // get code, error if empty 573 code := req.Form.Get("code") 574 if code == "" { 575 reply(http.StatusBadRequest, &AuthResult{ 576 Name: "Auth Error", 577 Description: "No code returned by remote server", 578 }) 579 return 580 } 581 582 // check state 583 state := req.Form.Get("state") 584 if state != s.state && !(state == "" && s.opt.StateBlankOK) { 585 reply(http.StatusBadRequest, &AuthResult{ 586 Name: "Auth state doesn't match", 587 Description: fmt.Sprintf("Expecting %q got %q", s.state, state), 588 }) 589 return 590 } 591 592 // code OK 593 reply(http.StatusOK, &AuthResult{ 594 OK: true, 595 Code: code, 596 Form: req.Form, 597 }) 598 } 599 600 // Init gets the internal web server ready to receive config details 601 func (s *authServer) Init() error { 602 fs.Debugf(nil, "Starting auth server on %s", s.bindAddress) 603 mux := http.NewServeMux() 604 s.server = &http.Server{ 605 Addr: s.bindAddress, 606 Handler: mux, 607 } 608 s.server.SetKeepAlivesEnabled(false) 609 610 mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, req *http.Request) { 611 http.Error(w, "", http.StatusNotFound) 612 return 613 }) 614 mux.HandleFunc("/auth", func(w http.ResponseWriter, req *http.Request) { 615 state := req.FormValue("state") 616 if state != s.state { 617 fs.Debugf(nil, "State did not match: want %q got %q", s.state, state) 618 http.Error(w, "State did not match - please try again", http.StatusForbidden) 619 return 620 } 621 http.Redirect(w, req, s.authURL, http.StatusTemporaryRedirect) 622 return 623 }) 624 mux.HandleFunc("/", s.handleAuth) 625 626 var err error 627 s.listener, err = net.Listen("tcp", s.bindAddress) 628 if err != nil { 629 return err 630 } 631 return nil 632 } 633 634 // Serve the auth server, doesn't return 635 func (s *authServer) Serve() { 636 err := s.server.Serve(s.listener) 637 fs.Debugf(nil, "Closed auth server with error: %v", err) 638 } 639 640 // Stop the auth server by closing its socket 641 func (s *authServer) Stop() { 642 fs.Debugf(nil, "Closing auth server") 643 close(s.result) 644 _ = s.listener.Close() 645 646 // close the server 647 _ = s.server.Close() 648 }