From f31ab6d17857cc5619f321f0d7146721099ba644 Mon Sep 17 00:00:00 2001 From: Fred Date: Wed, 11 Jan 2023 20:25:44 +0000 Subject: [PATCH] seafile: renew library password - fixes #6662 Passwords for encrypted libraries are kept in memory in the server and flushed after an hour. This MR fixes an issue when the library password expires after 1 hour. --- backend/seafile/renew.go | 54 +++++++++++++++++++ backend/seafile/renew_test.go | 35 ++++++++++++ backend/seafile/seafile.go | 16 ++++++ docs/content/seafile.md | 8 ++- .../init.d/seafile/docker-compose.yml | 4 +- 5 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 backend/seafile/renew.go create mode 100644 backend/seafile/renew_test.go diff --git a/backend/seafile/renew.go b/backend/seafile/renew.go new file mode 100644 index 000000000..a67159d52 --- /dev/null +++ b/backend/seafile/renew.go @@ -0,0 +1,54 @@ +package seafile + +import ( + "sync" + "time" + + "github.com/rclone/rclone/fs" +) + +// Renew allows tokens to be renewed on expiry. +type Renew struct { + ts *time.Ticker // timer indicating when it's time to renew the token + run func() error // the callback to do the renewal + done chan interface{} // channel to end the go routine + shutdown *sync.Once +} + +// NewRenew creates a new Renew struct and starts a background process +// which renews the token whenever it expires. It uses the run() call +// to do the renewal. +func NewRenew(every time.Duration, run func() error) *Renew { + r := &Renew{ + ts: time.NewTicker(every), + run: run, + done: make(chan interface{}), + shutdown: &sync.Once{}, + } + go r.renewOnExpiry() + return r +} + +func (r *Renew) renewOnExpiry() { + for { + select { + case <-r.ts.C: + err := r.run() + if err != nil { + fs.Errorf(nil, "error while refreshing decryption token: %s", err) + } + + case <-r.done: + return + } + } +} + +// Shutdown stops the ticker and no more renewal will take place. +func (r *Renew) Shutdown() { + // closing a channel can only be done once + r.shutdown.Do(func() { + r.ts.Stop() + close(r.done) + }) +} diff --git a/backend/seafile/renew_test.go b/backend/seafile/renew_test.go new file mode 100644 index 000000000..4e9c4e13b --- /dev/null +++ b/backend/seafile/renew_test.go @@ -0,0 +1,35 @@ +package seafile + +import ( + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestShouldAllowShutdownTwice(t *testing.T) { + renew := NewRenew(time.Hour, func() error { + return nil + }) + renew.Shutdown() + renew.Shutdown() +} + +func TestRenewal(t *testing.T) { + var count int64 + + wg := sync.WaitGroup{} + wg.Add(2) // run the renewal twice + renew := NewRenew(time.Millisecond, func() error { + atomic.AddInt64(&count, 1) + wg.Done() + return nil + }) + wg.Wait() + renew.Shutdown() + + // it is technically possible that a third renewal gets triggered between Wait() and Shutdown() + assert.GreaterOrEqual(t, atomic.LoadInt64(&count), int64(2)) +} diff --git a/backend/seafile/seafile.go b/backend/seafile/seafile.go index d1c2abdbc..58dfd79c2 100644 --- a/backend/seafile/seafile.go +++ b/backend/seafile/seafile.go @@ -143,6 +143,7 @@ type Fs struct { createDirMutex sync.Mutex // Protect creation of directories useOldDirectoryAPI bool // Use the old API v2 if seafile < 7 moveDirNotAvailable bool // Version < 7.0 don't have an API to move a directory + renew *Renew // Renew an encrypted library token } // ------------------------------------------------------------ @@ -268,6 +269,11 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e } // And remove the public link feature f.features.PublicLink = nil + + // renew the library password every 45 minutes + f.renew = NewRenew(45*time.Minute, func() error { + return f.authorizeLibrary(context.Background(), libraryID) + }) } } else { // Deactivate the cleaner feature since there's no library selected @@ -383,6 +389,15 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf return nil, fmt.Errorf("unknown state %q", config.State) } +// Shutdown the Fs +func (f *Fs) Shutdown(ctx context.Context) error { + if f.renew == nil { + return nil + } + f.renew.Shutdown() + return nil +} + // sets the AuthorizationToken up func (f *Fs) setAuthorizationToken(token string) { f.srv.SetHeader("Authorization", "Token "+token) @@ -1331,6 +1346,7 @@ var ( _ fs.PutStreamer = &Fs{} _ fs.PublicLinker = &Fs{} _ fs.UserInfoer = &Fs{} + _ fs.Shutdowner = &Fs{} _ fs.Object = &Object{} _ fs.IDer = &Object{} ) diff --git a/docs/content/seafile.md b/docs/content/seafile.md index bfa969a50..57399657a 100644 --- a/docs/content/seafile.md +++ b/docs/content/seafile.md @@ -8,9 +8,10 @@ versionIntroduced: "v1.52" This is a backend for the [Seafile](https://www.seafile.com/) storage service: - It works with both the free community edition or the professional edition. -- Seafile versions 6.x and 7.x are all supported. +- Seafile versions 6.x, 7.x, 8.x and 9.x are all supported. - Encrypted libraries are also supported. - It supports 2FA enabled users +- Using a Library API Token is **not** supported ## Configuration @@ -256,14 +257,17 @@ that has already been shared, you will get the exact same link. ### Compatibility -It has been actively tested using the [seafile docker image](https://github.com/haiwen/seafile-docker) of these versions: +It has been actively developed using the [seafile docker image](https://github.com/haiwen/seafile-docker) of these versions: - 6.3.4 community edition - 7.0.5 community edition - 7.1.3 community edition +- 9.0.10 community edition Versions below 6.0 are not supported. Versions between 6.0 and 6.3 haven't been tested and might not work properly. +Each new version of `rclone` is automatically tested against the [latest docker image](https://hub.docker.com/r/seafileltd/seafile-mc/) of the seafile community server. + {{< rem autogenerated options start" - DO NOT EDIT - instead edit fs.RegInfo in backend/seafile/seafile.go then run make backenddocs" >}} ### Standard options diff --git a/fstest/testserver/init.d/seafile/docker-compose.yml b/fstest/testserver/init.d/seafile/docker-compose.yml index 55d8e2f69..8e0a099cd 100644 --- a/fstest/testserver/init.d/seafile/docker-compose.yml +++ b/fstest/testserver/init.d/seafile/docker-compose.yml @@ -1,7 +1,7 @@ version: '2.0' services: db: - image: mariadb:10.1 + image: mariadb:10.5 environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} - MYSQL_LOG_CONSOLE=true @@ -9,7 +9,7 @@ services: - ${SEAFILE_TEST_DATA}/${NAME}/seafile-mysql/db:/var/lib/mysql memcached: - image: memcached:1.5.6 + image: memcached:1.6.9 entrypoint: memcached -m 256 seafile: