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  )