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 createDirMutex sync.Mutex // Protect creation of directories
useOldDirectoryAPI bool // Use the old API v2 if seafile < 7 useOldDirectoryAPI bool // Use the old API v2 if seafile < 7
moveDirNotAvailable bool // Version < 7.0 don't have an API to move a directory 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 // And remove the public link feature
f.features.PublicLink = nil 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 { } else {
// Deactivate the cleaner feature since there's no library selected // 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) 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 // sets the AuthorizationToken up
func (f *Fs) setAuthorizationToken(token string) { func (f *Fs) setAuthorizationToken(token string) {
f.srv.SetHeader("Authorization", "Token "+token) f.srv.SetHeader("Authorization", "Token "+token)
@ -1331,6 +1346,7 @@ var (
_ fs.PutStreamer = &Fs{} _ fs.PutStreamer = &Fs{}
_ fs.PublicLinker = &Fs{} _ fs.PublicLinker = &Fs{}
_ fs.UserInfoer = &Fs{} _ fs.UserInfoer = &Fs{}
_ fs.Shutdowner = &Fs{}
_ fs.Object = &Object{} _ fs.Object = &Object{}
_ fs.IDer = &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: 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. - 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. - Encrypted libraries are also supported.
- It supports 2FA enabled users - It supports 2FA enabled users
- Using a Library API Token is **not** supported
## Configuration ## Configuration
@ -256,14 +257,17 @@ that has already been shared, you will get the exact same link.
### Compatibility ### 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 - 6.3.4 community edition
- 7.0.5 community edition - 7.0.5 community edition
- 7.1.3 community edition - 7.1.3 community edition
- 9.0.10 community edition
Versions below 6.0 are not supported. Versions below 6.0 are not supported.
Versions between 6.0 and 6.3 haven't been tested and might not work properly. 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" >}} {{< rem autogenerated options start" - DO NOT EDIT - instead edit fs.RegInfo in backend/seafile/seafile.go then run make backenddocs" >}}
### Standard options ### Standard options

View File

@ -1,7 +1,7 @@
version: '2.0' version: '2.0'
services: services:
db: db:
image: mariadb:10.1 image: mariadb:10.5
environment: environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_LOG_CONSOLE=true - MYSQL_LOG_CONSOLE=true
@ -9,7 +9,7 @@ services:
- ${SEAFILE_TEST_DATA}/${NAME}/seafile-mysql/db:/var/lib/mysql - ${SEAFILE_TEST_DATA}/${NAME}/seafile-mysql/db:/var/lib/mysql
memcached: memcached:
image: memcached:1.5.6 image: memcached:1.6.9
entrypoint: memcached -m 256 entrypoint: memcached -m 256
seafile: seafile: