github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/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.New("empty token found - please run rclone config again") 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, err 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, err 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 // Config does the initial creation of the token 361 // 362 // It may run an internal webserver to receive the results 363 func Config(id, name string, m configmap.Mapper, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error { 364 return doConfig(id, name, m, config, true, nil, opts) 365 } 366 367 // CheckAuthFn is called when a good Auth has been received 368 type CheckAuthFn func(*oauth2.Config, *AuthResult) error 369 370 // ConfigWithCallback does the initial creation of the token 371 // 372 // It may run an internal webserver to receive the results 373 // 374 // When the AuthResult is known the checkAuth function is called if set 375 func ConfigWithCallback(id, name string, m configmap.Mapper, config *oauth2.Config, checkAuth CheckAuthFn, opts ...oauth2.AuthCodeOption) error { 376 return doConfig(id, name, m, config, true, checkAuth, opts) 377 } 378 379 // ConfigNoOffline does the same as Config but does not pass the 380 // "access_type=offline" parameter. 381 func ConfigNoOffline(id, name string, m configmap.Mapper, config *oauth2.Config, opts ...oauth2.AuthCodeOption) error { 382 return doConfig(id, name, m, config, false, nil, opts) 383 } 384 385 func doConfig(id, name string, m configmap.Mapper, oauthConfig *oauth2.Config, offline bool, checkAuth CheckAuthFn, opts []oauth2.AuthCodeOption) error { 386 oauthConfig, changed := overrideCredentials(name, m, oauthConfig) 387 authorizeOnlyValue, ok := m.Get(config.ConfigAuthorize) 388 authorizeOnly := ok && authorizeOnlyValue != "" // set if being run by "rclone authorize" 389 authorizeNoAutoBrowserValue, ok := m.Get(config.ConfigAuthNoBrowser) 390 authorizeNoAutoBrowser := ok && authorizeNoAutoBrowserValue != "" 391 392 // See if already have a token 393 tokenString, ok := m.Get("token") 394 if ok && tokenString != "" { 395 fmt.Printf("Already have a token - refresh?\n") 396 if !config.ConfirmWithConfig(m, "config_refresh_token", true) { 397 return nil 398 } 399 } 400 401 // Ask the user whether they are using a local machine 402 isLocal := func() bool { 403 fmt.Printf("Use auto config?\n") 404 fmt.Printf(" * Say Y if not sure\n") 405 fmt.Printf(" * Say N if you are working on a remote or headless machine\n") 406 return config.ConfirmWithConfig(m, "config_is_local", true) 407 } 408 409 // Detect whether we should use internal web server 410 useWebServer := false 411 switch oauthConfig.RedirectURL { 412 case TitleBarRedirectURL: 413 useWebServer = authorizeOnly 414 if !authorizeOnly { 415 useWebServer = isLocal() 416 } 417 if useWebServer { 418 // copy the config and set to use the internal webserver 419 configCopy := *oauthConfig 420 oauthConfig = &configCopy 421 oauthConfig.RedirectURL = RedirectURL 422 } 423 default: 424 if changed { 425 fmt.Printf("Make sure your Redirect URL is set to %q in your custom config.\n", oauthConfig.RedirectURL) 426 } 427 useWebServer = true 428 if authorizeOnly { 429 break 430 } 431 if !isLocal() { 432 fmt.Printf("For this to work, you will need rclone available on a machine that has a web browser available.\n") 433 fmt.Printf("Execute the following on your machine (same rclone version recommended) :\n") 434 if changed { 435 fmt.Printf("\trclone authorize %q %q %q\n", id, oauthConfig.ClientID, oauthConfig.ClientSecret) 436 } else { 437 fmt.Printf("\trclone authorize %q\n", id) 438 } 439 fmt.Println("Then paste the result below:") 440 code := config.ReadNonEmptyLine("result> ") 441 token := &oauth2.Token{} 442 err := json.Unmarshal([]byte(code), token) 443 if err != nil { 444 return err 445 } 446 return PutToken(name, m, token, true) 447 } 448 } 449 450 // Make random state 451 state, err := random.Password(128) 452 if err != nil { 453 return err 454 } 455 456 // Generate oauth URL 457 if offline { 458 opts = append(opts, oauth2.AccessTypeOffline) 459 } 460 authURL := oauthConfig.AuthCodeURL(state, opts...) 461 462 // Prepare webserver if needed 463 var server *authServer 464 if useWebServer { 465 server = newAuthServer(bindAddress, state, authURL) 466 err := server.Init() 467 if err != nil { 468 return errors.Wrap(err, "failed to start auth webserver") 469 } 470 go server.Serve() 471 defer server.Stop() 472 authURL = "http://" + bindAddress + "/auth?state=" + state 473 } 474 475 if !authorizeNoAutoBrowser && oauthConfig.RedirectURL != TitleBarRedirectURL { 476 // Open the URL for the user to visit 477 _ = open.Start(authURL) 478 fmt.Printf("If your browser doesn't open automatically go to the following link: %s\n", authURL) 479 } else { 480 fmt.Printf("Please go to the following link: %s\n", authURL) 481 } 482 fmt.Printf("Log in and authorize rclone for access\n") 483 484 // Read the code via the webserver or manually 485 var auth *AuthResult 486 if useWebServer { 487 fmt.Printf("Waiting for code...\n") 488 auth = <-server.result 489 if !auth.OK || auth.Code == "" { 490 return auth 491 } 492 fmt.Printf("Got code\n") 493 if checkAuth != nil { 494 err = checkAuth(oauthConfig, auth) 495 if err != nil { 496 return err 497 } 498 } 499 } else { 500 auth = &AuthResult{ 501 Code: config.ReadNonEmptyLine("Enter verification code> "), 502 } 503 } 504 505 // Exchange the code for a token 506 token, err := oauthConfig.Exchange(oauth2.NoContext, auth.Code) 507 if err != nil { 508 return errors.Wrap(err, "failed to get token") 509 } 510 511 // Print code if we are doing a manual auth 512 if authorizeOnly { 513 result, err := json.Marshal(token) 514 if err != nil { 515 return errors.Wrap(err, "failed to marshal token") 516 } 517 fmt.Printf("Paste the following into your remote machine --->\n%s\n<---End paste\n", result) 518 } 519 return PutToken(name, m, token, true) 520 } 521 522 // Local web server for collecting auth 523 type authServer struct { 524 state string 525 listener net.Listener 526 bindAddress string 527 authURL string 528 server *http.Server 529 result chan *AuthResult 530 } 531 532 // newAuthServer makes the webserver for collecting auth 533 func newAuthServer(bindAddress, state, authURL string) *authServer { 534 return &authServer{ 535 state: state, 536 bindAddress: bindAddress, 537 authURL: authURL, // http://host/auth redirects to here 538 result: make(chan *AuthResult, 1), 539 } 540 } 541 542 // Receive the auth request 543 func (s *authServer) handleAuth(w http.ResponseWriter, req *http.Request) { 544 fs.Debugf(nil, "Received %s request on auth server to %q", req.Method, req.URL.Path) 545 546 // Reply with the response to the user and to the channel 547 reply := func(status int, res *AuthResult) { 548 w.WriteHeader(status) 549 w.Header().Set("Content-Type", "text/html") 550 var t = template.Must(template.New("authResponse").Parse(AuthResponseTemplate)) 551 if err := t.Execute(w, res); err != nil { 552 fs.Debugf(nil, "Could not execute template for web response.") 553 } 554 s.result <- res 555 } 556 557 // Parse the form parameters and save them 558 err := req.ParseForm() 559 if err != nil { 560 reply(http.StatusBadRequest, &AuthResult{ 561 Name: "Parse form error", 562 Description: err.Error(), 563 }) 564 return 565 } 566 567 // get code, error if empty 568 code := req.Form.Get("code") 569 if code == "" { 570 reply(http.StatusBadRequest, &AuthResult{ 571 Name: "Auth Error", 572 Description: "No code returned by remote server", 573 }) 574 return 575 } 576 577 // check state 578 state := req.Form.Get("state") 579 if state != s.state { 580 reply(http.StatusBadRequest, &AuthResult{ 581 Name: "Auth state doesn't match", 582 Description: fmt.Sprintf("Expecting %q got %q", s.state, state), 583 }) 584 return 585 } 586 587 // code OK 588 reply(http.StatusOK, &AuthResult{ 589 OK: true, 590 Code: code, 591 Form: req.Form, 592 }) 593 } 594 595 // Init gets the internal web server ready to receive config details 596 func (s *authServer) Init() error { 597 fs.Debugf(nil, "Starting auth server on %s", s.bindAddress) 598 mux := http.NewServeMux() 599 s.server = &http.Server{ 600 Addr: s.bindAddress, 601 Handler: mux, 602 } 603 s.server.SetKeepAlivesEnabled(false) 604 605 mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, req *http.Request) { 606 http.Error(w, "", http.StatusNotFound) 607 return 608 }) 609 mux.HandleFunc("/auth", func(w http.ResponseWriter, req *http.Request) { 610 state := req.FormValue("state") 611 if state != s.state { 612 fs.Debugf(nil, "State did not match: want %q got %q", s.state, state) 613 http.Error(w, "State did not match - please try again", http.StatusForbidden) 614 return 615 } 616 http.Redirect(w, req, s.authURL, http.StatusTemporaryRedirect) 617 return 618 }) 619 mux.HandleFunc("/", s.handleAuth) 620 621 var err error 622 s.listener, err = net.Listen("tcp", s.bindAddress) 623 if err != nil { 624 return err 625 } 626 return nil 627 } 628 629 // Serve the auth server, doesn't return 630 func (s *authServer) Serve() { 631 err := s.server.Serve(s.listener) 632 fs.Debugf(nil, "Closed auth server with error: %v", err) 633 } 634 635 // Stop the auth server by closing its socket 636 func (s *authServer) Stop() { 637 fs.Debugf(nil, "Closing auth server") 638 close(s.result) 639 _ = s.listener.Close() 640 641 // close the server 642 _ = s.server.Close() 643 }