feat(download-clients): rtorrent support Digest Auth (#1596)

* feat(download-clients): rtorrent support basic auth

* feat(download-client): implement new auth logic

* fix(download-client): tests store

* chore(deps): update go-rtorrent to v1.11.0
This commit is contained in:
ze0s 2024-08-28 12:21:56 +02:00 committed by GitHub
parent 861f30c144
commit 0d53f7e5fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 231 additions and 71 deletions

View file

@ -142,6 +142,7 @@ func (r *DownloadClientRepo) Store(ctx context.Context, client *domain.DownloadC
Rules: client.Settings.Rules,
ExternalDownloadClientId: client.Settings.ExternalDownloadClientId,
ExternalDownloadClient: client.Settings.ExternalDownloadClient,
Auth: client.Settings.Auth,
}
settingsJson, err := json.Marshal(&settings)
@ -177,6 +178,7 @@ func (r *DownloadClientRepo) Update(ctx context.Context, client *domain.Download
Rules: client.Settings.Rules,
ExternalDownloadClientId: client.Settings.ExternalDownloadClientId,
ExternalDownloadClient: client.Settings.ExternalDownloadClient,
Auth: client.Settings.Auth,
}
settingsJson, err := json.Marshal(&settings)

View file

@ -44,6 +44,12 @@ func getMockDownloadClient() domain.DownloadClient {
},
ExternalDownloadClientId: 0,
ExternalDownloadClient: "",
Auth: domain.DownloadClientAuth{
Enabled: true,
Type: domain.DownloadClientAuthTypeBasic,
Username: "username",
Password: "password",
},
},
}
}

View file

@ -5,6 +5,7 @@ package domain
import (
"context"
"encoding/json"
"fmt"
"net/url"
@ -38,10 +39,72 @@ type DownloadClient struct {
type DownloadClientSettings struct {
APIKey string `json:"apikey,omitempty"`
Basic BasicAuth `json:"basic,omitempty"`
Basic BasicAuth `json:"basic,omitempty"` // Deprecated: Use Auth instead
Rules DownloadClientRules `json:"rules,omitempty"`
ExternalDownloadClientId int `json:"external_download_client_id,omitempty"`
ExternalDownloadClient string `json:"external_download_client,omitempty"`
Auth DownloadClientAuth `json:"auth,omitempty"`
}
// MarshalJSON Custom method to translate Basic into Auth without including Basic in JSON output
func (dcs *DownloadClientSettings) MarshalJSON() ([]byte, error) {
// Ensuring Auth is updated with Basic info before marshaling if Basic is set
if dcs.Basic.Username != "" || dcs.Basic.Password != "" {
dcs.Auth = DownloadClientAuth{
Enabled: dcs.Basic.Auth,
Type: DownloadClientAuthTypeBasic,
Username: dcs.Basic.Username,
Password: dcs.Basic.Password,
}
}
type Alias DownloadClientSettings
return json.Marshal(&struct {
*Alias
}{
Alias: (*Alias)(dcs),
})
}
// UnmarshalJSON Custom method to translate Basic into Auth
func (dcs *DownloadClientSettings) UnmarshalJSON(data []byte) error {
type Alias DownloadClientSettings
aux := &struct {
*Alias
}{
Alias: (*Alias)(dcs),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
// If Basic fields are not empty, populate Auth fields accordingly
if aux.Basic.Username != "" || aux.Basic.Password != "" {
dcs.Auth = DownloadClientAuth{
Enabled: aux.Basic.Auth,
Type: DownloadClientAuthTypeBasic,
Username: aux.Basic.Username,
Password: aux.Basic.Password,
}
}
return nil
}
type DownloadClientAuthType string
const (
DownloadClientAuthTypeNone = "NONE"
DownloadClientAuthTypeBasic = "BASIC_AUTH"
DownloadClientAuthTypeDigest = "DIGEST_AUTH"
)
type DownloadClientAuth struct {
Enabled bool `json:"enabled,omitempty"`
Type DownloadClientAuthType `json:"type,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
type DownloadClientRules struct {

View file

@ -1,3 +1,6 @@
// Copyright (c) 2021 - 2024, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later
package download_client
import (

View file

@ -5,7 +5,9 @@ package download_client
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"net/url"
"time"
@ -24,6 +26,7 @@ import (
"github.com/autobrr/go-qbittorrent"
"github.com/autobrr/go-rtorrent"
"github.com/dcarbone/zadapters/zstdlog"
"github.com/icholy/digest"
"github.com/rs/zerolog"
)
@ -70,16 +73,16 @@ func (s *service) testConnection(ctx context.Context, client domain.DownloadClie
func (s *service) testQbittorrentConnection(ctx context.Context, client domain.DownloadClient) error {
qbtSettings := qbittorrent.Config{
Host: client.BuildLegacyHost(),
TLSSkipVerify: client.TLSSkipVerify,
Username: client.Username,
Password: client.Password,
TLSSkipVerify: client.TLSSkipVerify,
Log: s.subLogger,
}
// only set basic auth if enabled
if client.Settings.Basic.Auth {
qbtSettings.BasicUser = client.Settings.Basic.Username
qbtSettings.BasicPass = client.Settings.Basic.Password
if client.Settings.Auth.Enabled {
qbtSettings.BasicUser = client.Settings.Auth.Username
qbtSettings.BasicPass = client.Settings.Auth.Password
}
qbt := qbittorrent.NewClient(qbtSettings)
@ -155,14 +158,31 @@ func (s *service) testDelugeConnection(ctx context.Context, client domain.Downlo
}
func (s *service) testRTorrentConnection(ctx context.Context, client domain.DownloadClient) error {
// create client
rt := rtorrent.NewClient(rtorrent.Config{
cfg := rtorrent.Config{
Addr: client.Host,
TLSSkipVerify: client.TLSSkipVerify,
BasicUser: client.Settings.Basic.Username,
BasicPass: client.Settings.Basic.Password,
Log: nil,
})
BasicUser: client.Settings.Auth.Username,
BasicPass: client.Settings.Auth.Password,
Log: s.subLogger,
}
// create client
rt := rtorrent.NewClient(cfg)
if client.Settings.Auth.Type == domain.DownloadClientAuthTypeDigest {
httpClient := &http.Client{
Transport: &digest.Transport{
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: client.TLSSkipVerify},
},
},
}
// override client
rt = rtorrent.NewClientWithOpts(cfg, rtorrent.WithCustomClient(httpClient))
}
name, err := rt.Name(ctx)
if err != nil {
@ -206,7 +226,7 @@ func (s *service) testTransmissionConnection(ctx context.Context, client domain.
return errors.Wrap(err, "error getting rpc info: %v", client.Host)
}
s.log.Debug().Msgf("test client connection for Transmission: got version: %v", version)
s.log.Trace().Msgf("test client connection for Transmission: got version: %v", version)
s.log.Debug().Msgf("test client connection for Transmission: success")
@ -217,9 +237,9 @@ func (s *service) testRadarrConnection(ctx context.Context, client domain.Downlo
r := radarr.New(radarr.Config{
Hostname: client.Host,
APIKey: client.Settings.APIKey,
BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password,
BasicAuth: client.Settings.Auth.Enabled,
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
Log: s.subLogger,
})
@ -236,9 +256,9 @@ func (s *service) testSonarrConnection(ctx context.Context, client domain.Downlo
r := sonarr.New(sonarr.Config{
Hostname: client.Host,
APIKey: client.Settings.APIKey,
BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password,
BasicAuth: client.Settings.Auth.Enabled,
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
Log: s.subLogger,
})
@ -255,9 +275,9 @@ func (s *service) testLidarrConnection(ctx context.Context, client domain.Downlo
r := lidarr.New(lidarr.Config{
Hostname: client.Host,
APIKey: client.Settings.APIKey,
BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password,
BasicAuth: client.Settings.Auth.Enabled,
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
Log: s.subLogger,
})
@ -274,9 +294,9 @@ func (s *service) testWhisparrConnection(ctx context.Context, client domain.Down
r := whisparr.New(whisparr.Config{
Hostname: client.Host,
APIKey: client.Settings.APIKey,
BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password,
BasicAuth: client.Settings.Auth.Enabled,
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
Log: s.subLogger,
})
@ -293,9 +313,9 @@ func (s *service) testReadarrConnection(ctx context.Context, client domain.Downl
r := readarr.New(readarr.Config{
Hostname: client.Host,
APIKey: client.Settings.APIKey,
BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password,
BasicAuth: client.Settings.Auth.Enabled,
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
Log: s.subLogger,
})
@ -310,8 +330,12 @@ func (s *service) testReadarrConnection(ctx context.Context, client domain.Downl
func (s *service) testPorlaConnection(client domain.DownloadClient) error {
p := porla.NewClient(porla.Config{
Hostname: client.Host,
AuthToken: client.Settings.APIKey,
Hostname: client.Host,
TLSSkipVerify: client.TLSSkipVerify,
AuthToken: client.Settings.APIKey,
BasicUser: client.Settings.Auth.Username,
BasicPass: client.Settings.Auth.Password,
Log: s.subLogger,
})
version, err := p.Version()
@ -320,13 +344,13 @@ func (s *service) testPorlaConnection(client domain.DownloadClient) error {
return errors.Wrap(err, "porla: failed to get version: %v", client.Host)
}
commitish := version.Commitish
commitHash := version.Commitish
if len(commitish) > 8 {
commitish = commitish[:8]
if len(commitHash) > 8 {
commitHash = commitHash[:8]
}
s.log.Debug().Msgf("test client connection for porla: found version %s (commit %s)", version.Version, commitish)
s.log.Debug().Msgf("test client connection for porla: found version %s (commit %s)", version.Version, commitHash)
return nil
}
@ -335,9 +359,9 @@ func (s *service) testSabnzbdConnection(ctx context.Context, client domain.Downl
opts := sabnzbd.Options{
Addr: client.Host,
ApiKey: client.Settings.APIKey,
BasicUser: client.Settings.Basic.Username,
BasicPass: client.Settings.Basic.Password,
Log: nil,
BasicUser: client.Settings.Auth.Username,
BasicPass: client.Settings.Auth.Password,
Log: s.subLogger,
}
sab := sabnzbd.New(opts)

View file

@ -5,8 +5,10 @@ package download_client
import (
"context"
"crypto/tls"
"fmt"
"log"
"net/http"
"net/url"
"sync"
"time"
@ -27,6 +29,7 @@ import (
"github.com/autobrr/go-qbittorrent"
"github.com/autobrr/go-rtorrent"
"github.com/dcarbone/zadapters/zstdlog"
"github.com/icholy/digest"
"github.com/rs/zerolog"
)
@ -184,8 +187,8 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
Password: client.Password,
TLSSkipVerify: client.TLSSkipVerify,
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "qBittorrent").Str("client", client.Name).Logger(), zerolog.TraceLevel),
BasicUser: client.Settings.Basic.Username,
BasicPass: client.Settings.Basic.Password,
BasicUser: client.Settings.Auth.Username,
BasicPass: client.Settings.Auth.Password,
})
case domain.DownloadClientTypePorla:
@ -193,8 +196,8 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
Hostname: client.Host,
AuthToken: client.Settings.APIKey,
TLSSkipVerify: client.TLSSkipVerify,
BasicUser: client.Settings.Basic.Username,
BasicPass: client.Settings.Basic.Password,
BasicUser: client.Settings.Auth.Username,
BasicPass: client.Settings.Auth.Password,
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Porla").Str("client", client.Name).Logger(), zerolog.TraceLevel),
})
@ -241,22 +244,46 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
client.Client = tbt
case domain.DownloadClientTypeRTorrent:
client.Client = rtorrent.NewClient(rtorrent.Config{
Addr: client.Host,
TLSSkipVerify: client.TLSSkipVerify,
BasicUser: client.Settings.Basic.Username,
BasicPass: client.Settings.Basic.Password,
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "rTorrent").Str("client", client.Name).Logger(), zerolog.TraceLevel),
})
if client.Settings.Auth.Type == domain.DownloadClientAuthTypeDigest {
cfg := rtorrent.Config{
Addr: client.Host,
TLSSkipVerify: client.TLSSkipVerify,
BasicUser: client.Settings.Auth.Username,
BasicPass: client.Settings.Auth.Password,
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "rTorrent").Str("client", client.Name).Logger(), zerolog.TraceLevel),
}
httpClient := &http.Client{
Transport: &digest.Transport{
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: client.TLSSkipVerify},
},
},
}
// override client
client.Client = rtorrent.NewClientWithOpts(cfg, rtorrent.WithCustomClient(httpClient))
} else {
client.Client = rtorrent.NewClient(rtorrent.Config{
Addr: client.Host,
TLSSkipVerify: client.TLSSkipVerify,
BasicUser: client.Settings.Auth.Username,
BasicPass: client.Settings.Auth.Password,
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "rTorrent").Str("client", client.Name).Logger(), zerolog.TraceLevel),
})
}
case domain.DownloadClientTypeLidarr:
client.Client = lidarr.New(lidarr.Config{
Hostname: client.Host,
APIKey: client.Settings.APIKey,
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Lidarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password,
BasicAuth: client.Settings.Auth.Enabled,
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
})
case domain.DownloadClientTypeRadarr:
@ -264,9 +291,9 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
Hostname: client.Host,
APIKey: client.Settings.APIKey,
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Radarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password,
BasicAuth: client.Settings.Auth.Enabled,
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
})
case domain.DownloadClientTypeReadarr:
@ -274,9 +301,9 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
Hostname: client.Host,
APIKey: client.Settings.APIKey,
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Readarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password,
BasicAuth: client.Settings.Auth.Enabled,
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
})
case domain.DownloadClientTypeSonarr:
@ -284,9 +311,9 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
Hostname: client.Host,
APIKey: client.Settings.APIKey,
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Sonarr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password,
BasicAuth: client.Settings.Auth.Enabled,
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
})
case domain.DownloadClientTypeWhisparr:
@ -294,9 +321,9 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
Hostname: client.Host,
APIKey: client.Settings.APIKey,
Log: zstdlog.NewStdLoggerWithLevel(s.log.With().Str("type", "Whisparr").Str("client", client.Name).Logger(), zerolog.TraceLevel),
BasicAuth: client.Settings.Basic.Auth,
Username: client.Settings.Basic.Username,
Password: client.Settings.Basic.Password,
BasicAuth: client.Settings.Auth.Enabled,
Username: client.Settings.Auth.Username,
Password: client.Settings.Auth.Password,
})
case domain.DownloadClientTypeSabnzbd:
@ -304,8 +331,8 @@ func (s *service) GetClient(ctx context.Context, clientId int32) (*domain.Downlo
Addr: client.Host,
ApiKey: client.Settings.APIKey,
Log: nil,
BasicUser: client.Settings.Basic.Username,
BasicPass: client.Settings.Basic.Password,
BasicUser: client.Settings.Auth.Username,
BasicPass: client.Settings.Auth.Password,
})
}