diff --git a/drive/drive.go b/drive/drive.go index 071e429f7..133cf9790 100644 --- a/drive/drive.go +++ b/drive/drive.go @@ -16,7 +16,6 @@ package drive // * files with / in name import ( - "encoding/json" "fmt" "io" "log" @@ -28,9 +27,9 @@ import ( "sync" "time" - "code.google.com/p/goauth2/oauth" "code.google.com/p/google-api-go-client/drive/v2" "github.com/ncw/rclone/fs" + "github.com/ncw/rclone/googleauth" "github.com/ogier/pflag" ) @@ -47,14 +46,22 @@ const ( var ( // Flags driveFullList = pflag.BoolP("drive-full-list", "", true, "Use a full listing for directory list. More data but usually quicker.") + // Description of how to auth for this app + driveAuth = &googleauth.Auth{ + Scope: "https://www.googleapis.com/auth/drive", + DefaultClientId: rcloneClientId, + DefaultClientSecret: rcloneClientSecret, + } ) // Register with Fs func init() { fs.Register(&fs.FsInfo{ - Name: "drive", - NewFs: NewFs, - Config: Config, + Name: "drive", + NewFs: NewFs, + Config: func(name string) { + driveAuth.Config(name) + }, Options: []fs.Option{{ Name: "client_id", Help: "Google Application Client Id - leave blank to use rclone's.", @@ -65,77 +72,6 @@ func init() { }) } -// Configuration helper - called after the user has put in the defaults -func Config(name string) { - // See if already have a token - tokenString := fs.ConfigFile.MustValue(name, "token") - if tokenString != "" { - fmt.Printf("Already have a drive token - refresh?\n") - if !fs.Confirm() { - return - } - } - - // Get a drive transport - t, err := newDriveTransport(name) - if err != nil { - log.Fatalf("Couldn't make drive transport: %v", err) - } - - // Generate a URL for the user to visit for authorization. - authUrl := t.Config.AuthCodeURL("state") - fmt.Printf("Go to the following link in your browser\n") - fmt.Printf("%s\n", authUrl) - fmt.Printf("Log in, then type paste the token that is returned in the browser here\n") - - // Read the code, and exchange it for a token. - fmt.Printf("Enter verification code> ") - authCode := fs.ReadLine() - _, err = t.Exchange(authCode) - if err != nil { - log.Fatalf("Failed to get token: %v", err) - } - -} - -// A token cache to save the token in the config file section named -type tokenCache string - -// Get the token from the config file - returns an error if it isn't present -func (name tokenCache) Token() (*oauth.Token, error) { - tokenString, err := fs.ConfigFile.GetValue(string(name), "token") - if err != nil { - return nil, err - } - if tokenString == "" { - return nil, fmt.Errorf("Empty token found - please reconfigure") - } - token := new(oauth.Token) - err = json.Unmarshal([]byte(tokenString), token) - if err != nil { - return nil, err - } - return token, nil - -} - -// Save the token to the config file -// -// This saves the config file if it changes -func (name tokenCache) PutToken(token *oauth.Token) error { - tokenBytes, err := json.Marshal(token) - if err != nil { - return err - } - tokenString := string(tokenBytes) - old := fs.ConfigFile.MustValue(string(name), "token") - if tokenString != old { - fs.ConfigFile.SetValue(string(name), "token", tokenString) - fs.SaveConfig() - } - return nil -} - // FsDrive represents a remote drive server type FsDrive struct { svc *drive.Service // the connection to the drive server @@ -268,39 +204,9 @@ OUTER: return } -// Makes a new drive transport from the config -func newDriveTransport(name string) (*oauth.Transport, error) { - clientId := fs.ConfigFile.MustValue(name, "client_id") - if clientId == "" { - clientId = rcloneClientId - } - clientSecret := fs.ConfigFile.MustValue(name, "client_secret") - if clientSecret == "" { - clientSecret = rcloneClientSecret - } - - // Settings for authorization. - var driveConfig = &oauth.Config{ - ClientId: clientId, - ClientSecret: clientSecret, - Scope: "https://www.googleapis.com/auth/drive", - RedirectURL: "urn:ietf:wg:oauth:2.0:oob", - AuthURL: "https://accounts.google.com/o/oauth2/auth", - TokenURL: "https://accounts.google.com/o/oauth2/token", - TokenCache: tokenCache(name), - } - - t := &oauth.Transport{ - Config: driveConfig, - Transport: http.DefaultTransport, - } - - return t, nil -} - // NewFs contstructs an FsDrive from the path, container:path func NewFs(name, path string) (fs.Fs, error) { - t, err := newDriveTransport(name) + t, err := driveAuth.NewTransport(name) if err != nil { return nil, err } @@ -309,18 +215,12 @@ func NewFs(name, path string) (fs.Fs, error) { if err != nil { return nil, err } + f := &FsDrive{ root: root, dirCache: newDirCache(), } - // Try to pull the token from the cache; if this fails, we need to get one. - token, err := t.Config.TokenCache.Token() - if err != nil { - return nil, fmt.Errorf("Failed to get token: %s", err) - } - t.Token = token - // Create a new authorized Drive client. f.client = t.Client() f.svc, err = drive.New(f.client) diff --git a/googleauth/googleauth.go b/googleauth/googleauth.go new file mode 100644 index 000000000..113d53b1d --- /dev/null +++ b/googleauth/googleauth.go @@ -0,0 +1,138 @@ +// Common authentication between Google Drive and Google Cloud Storage +package googleauth + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "code.google.com/p/goauth2/oauth" + "github.com/ncw/rclone/fs" +) + +// A token cache to save the token in the config file section named +type TokenCache string + +// Get the token from the config file - returns an error if it isn't present +func (name TokenCache) Token() (*oauth.Token, error) { + tokenString, err := fs.ConfigFile.GetValue(string(name), "token") + if err != nil { + return nil, err + } + if tokenString == "" { + return nil, fmt.Errorf("Empty token found - please reconfigure") + } + token := new(oauth.Token) + err = json.Unmarshal([]byte(tokenString), token) + if err != nil { + return nil, err + } + return token, nil + +} + +// Save the token to the config file +// +// This saves the config file if it changes +func (name TokenCache) PutToken(token *oauth.Token) error { + tokenBytes, err := json.Marshal(token) + if err != nil { + return err + } + tokenString := string(tokenBytes) + old := fs.ConfigFile.MustValue(string(name), "token") + if tokenString != old { + fs.ConfigFile.SetValue(string(name), "token", tokenString) + fs.SaveConfig() + } + return nil +} + +// Auth contains information to authenticate an app against google services +type Auth struct { + Scope string + DefaultClientId string + DefaultClientSecret string +} + +// Makes a new transport using authorisation from the config +// +// Doesn't have a token yet +func (auth *Auth) newTransport(name string) (*oauth.Transport, error) { + clientId := fs.ConfigFile.MustValue(name, "client_id") + if clientId == "" { + clientId = auth.DefaultClientId + } + clientSecret := fs.ConfigFile.MustValue(name, "client_secret") + if clientSecret == "" { + clientSecret = auth.DefaultClientSecret + } + + // Settings for authorization. + var config = &oauth.Config{ + ClientId: clientId, + ClientSecret: clientSecret, + Scope: auth.Scope, + RedirectURL: "urn:ietf:wg:oauth:2.0:oob", + AuthURL: "https://accounts.google.com/o/oauth2/auth", + TokenURL: "https://accounts.google.com/o/oauth2/token", + TokenCache: TokenCache(name), + } + + t := &oauth.Transport{ + Config: config, + Transport: http.DefaultTransport, + } + + return t, nil +} + +// Makes a new transport using authorisation from the config with token +func (auth *Auth) NewTransport(name string) (*oauth.Transport, error) { + t, err := auth.newTransport(name) + if err != nil { + return nil, err + } + + // Try to pull the token from the cache; if this fails, we need to get one. + token, err := t.Config.TokenCache.Token() + if err != nil { + return nil, fmt.Errorf("Failed to get token: %s", err) + } + t.Token = token + + return t, nil +} + +// Configuration helper - called after the user has put in the defaults +func (auth *Auth) Config(name string) { + // See if already have a token + tokenString := fs.ConfigFile.MustValue(name, "token") + if tokenString != "" { + fmt.Printf("Already have a token - refresh?\n") + if !fs.Confirm() { + return + } + } + + // Get a transport + t, err := auth.newTransport(name) + if err != nil { + log.Fatalf("Couldn't make transport: %v", err) + } + + // Generate a URL for the user to visit for authorization. + authUrl := t.Config.AuthCodeURL("state") + fmt.Printf("Go to the following link in your browser\n") + fmt.Printf("%s\n", authUrl) + fmt.Printf("Log in, then type paste the token that is returned in the browser here\n") + + // Read the code, and exchange it for a token. + fmt.Printf("Enter verification code> ") + authCode := fs.ReadLine() + _, err = t.Exchange(authCode) + if err != nil { + log.Fatalf("Failed to get token: %v", err) + } +}