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