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  )