github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/hubic/hubic.go (about) 1 // Package hubic provides an interface to the Hubic object storage 2 // system. 3 package hubic 4 5 // This uses the normal swift mechanism to update the credentials and 6 // ignores the expires field returned by the Hubic API. This may need 7 // to be revisted after some actual experience. 8 9 import ( 10 "encoding/json" 11 "fmt" 12 "io/ioutil" 13 "log" 14 "net/http" 15 "strings" 16 "time" 17 18 "github.com/ncw/rclone/backend/swift" 19 "github.com/ncw/rclone/fs" 20 "github.com/ncw/rclone/fs/config" 21 "github.com/ncw/rclone/fs/config/configmap" 22 "github.com/ncw/rclone/fs/config/configstruct" 23 "github.com/ncw/rclone/fs/config/obscure" 24 "github.com/ncw/rclone/fs/fshttp" 25 "github.com/ncw/rclone/lib/oauthutil" 26 swiftLib "github.com/ncw/swift" 27 "github.com/pkg/errors" 28 "golang.org/x/oauth2" 29 ) 30 31 const ( 32 rcloneClientID = "api_hubic_svWP970PvSWbw5G3PzrAqZ6X2uHeZBPI" 33 rcloneEncryptedClientSecret = "leZKCcqy9movLhDWLVXX8cSLp_FzoiAPeEJOIOMRw1A5RuC4iLEPDYPWVF46adC_MVonnLdVEOTHVstfBOZ_lY4WNp8CK_YWlpRZ9diT5YI" 34 ) 35 36 // Globals 37 var ( 38 // Description of how to auth for this app 39 oauthConfig = &oauth2.Config{ 40 Scopes: []string{ 41 "credentials.r", // Read Openstack credentials 42 }, 43 Endpoint: oauth2.Endpoint{ 44 AuthURL: "https://api.hubic.com/oauth/auth/", 45 TokenURL: "https://api.hubic.com/oauth/token/", 46 }, 47 ClientID: rcloneClientID, 48 ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret), 49 RedirectURL: oauthutil.RedirectLocalhostURL, 50 } 51 ) 52 53 // Register with Fs 54 func init() { 55 fs.Register(&fs.RegInfo{ 56 Name: "hubic", 57 Description: "Hubic", 58 NewFs: NewFs, 59 Config: func(name string, m configmap.Mapper) { 60 err := oauthutil.Config("hubic", name, m, oauthConfig) 61 if err != nil { 62 log.Fatalf("Failed to configure token: %v", err) 63 } 64 }, 65 Options: append([]fs.Option{{ 66 Name: config.ConfigClientID, 67 Help: "Hubic Client Id\nLeave blank normally.", 68 }, { 69 Name: config.ConfigClientSecret, 70 Help: "Hubic Client Secret\nLeave blank normally.", 71 }}, swift.SharedOptions...), 72 }) 73 } 74 75 // credentials is the JSON returned from the Hubic API to read the 76 // OpenStack credentials 77 type credentials struct { 78 Token string `json:"token"` // Openstack token 79 Endpoint string `json:"endpoint"` // Openstack endpoint 80 Expires string `json:"expires"` // Expires date - eg "2015-11-09T14:24:56+01:00" 81 } 82 83 // Fs represents a remote hubic 84 type Fs struct { 85 fs.Fs // wrapped Fs 86 features *fs.Features // optional features 87 client *http.Client // client for oauth api 88 credentials credentials // returned from the Hubic API 89 expires time.Time // time credentials expire 90 } 91 92 // Object describes a swift object 93 type Object struct { 94 *swift.Object 95 } 96 97 // Return a string version 98 func (o *Object) String() string { 99 if o == nil { 100 return "<nil>" 101 } 102 return o.Object.String() 103 } 104 105 // ------------------------------------------------------------ 106 107 // String converts this Fs to a string 108 func (f *Fs) String() string { 109 if f.Fs == nil { 110 return "Hubic" 111 } 112 return fmt.Sprintf("Hubic %s", f.Fs.String()) 113 } 114 115 // getCredentials reads the OpenStack Credentials using the Hubic API 116 // 117 // The credentials are read into the Fs 118 func (f *Fs) getCredentials() (err error) { 119 req, err := http.NewRequest("GET", "https://api.hubic.com/1.0/account/credentials", nil) 120 if err != nil { 121 return err 122 } 123 resp, err := f.client.Do(req) 124 if err != nil { 125 return err 126 } 127 defer fs.CheckClose(resp.Body, &err) 128 if resp.StatusCode < 200 || resp.StatusCode > 299 { 129 body, _ := ioutil.ReadAll(resp.Body) 130 bodyStr := strings.TrimSpace(strings.Replace(string(body), "\n", " ", -1)) 131 return errors.Errorf("failed to get credentials: %s: %s", resp.Status, bodyStr) 132 } 133 decoder := json.NewDecoder(resp.Body) 134 var result credentials 135 err = decoder.Decode(&result) 136 if err != nil { 137 return err 138 } 139 // fs.Debugf(f, "Got credentials %+v", result) 140 if result.Token == "" || result.Endpoint == "" || result.Expires == "" { 141 return errors.New("couldn't read token, result and expired from credentials") 142 } 143 f.credentials = result 144 expires, err := time.Parse(time.RFC3339, result.Expires) 145 if err != nil { 146 return err 147 } 148 f.expires = expires 149 fs.Debugf(f, "Got swift credentials (expiry %v in %v)", f.expires, f.expires.Sub(time.Now())) 150 return nil 151 } 152 153 // NewFs constructs an Fs from the path, container:path 154 func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) { 155 client, _, err := oauthutil.NewClient(name, m, oauthConfig) 156 if err != nil { 157 return nil, errors.Wrap(err, "failed to configure Hubic") 158 } 159 160 f := &Fs{ 161 client: client, 162 } 163 164 // Make the swift Connection 165 c := &swiftLib.Connection{ 166 Auth: newAuth(f), 167 ConnectTimeout: 10 * fs.Config.ConnectTimeout, // Use the timeouts in the transport 168 Timeout: 10 * fs.Config.Timeout, // Use the timeouts in the transport 169 Transport: fshttp.NewTransport(fs.Config), 170 } 171 err = c.Authenticate() 172 if err != nil { 173 return nil, errors.Wrap(err, "error authenticating swift connection") 174 } 175 176 // Parse config into swift.Options struct 177 opt := new(swift.Options) 178 err = configstruct.Set(m, opt) 179 if err != nil { 180 return nil, err 181 } 182 183 // Make inner swift Fs from the connection 184 swiftFs, err := swift.NewFsWithConnection(opt, name, root, c, true) 185 if err != nil && err != fs.ErrorIsFile { 186 return nil, err 187 } 188 f.Fs = swiftFs 189 f.features = f.Fs.Features().Wrap(f) 190 return f, err 191 } 192 193 // Features returns the optional features of this Fs 194 func (f *Fs) Features() *fs.Features { 195 return f.features 196 } 197 198 // UnWrap returns the Fs that this Fs is wrapping 199 func (f *Fs) UnWrap() fs.Fs { 200 return f.Fs 201 } 202 203 // Check the interfaces are satisfied 204 var ( 205 _ fs.Fs = (*Fs)(nil) 206 _ fs.UnWrapper = (*Fs)(nil) 207 )