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.
This commit is contained in:
Fred 2023-01-11 20:25:44 +00:00 committed by Nick Craig-Wood
parent f08bb5bf66
commit f31ab6d178
5 changed files with 113 additions and 4 deletions

54
backend/seafile/renew.go Normal file
View File

@ -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)
})
}

View File

@ -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))
}

View File

@ -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{}
)

View File

@ -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

View File

@ -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: