diff --git a/public/job/job.go b/public/job/job.go index 201954a..bd8d604 100644 --- a/public/job/job.go +++ b/public/job/job.go @@ -16,7 +16,7 @@ const ( TypeRecording Type = "recording" ) -const minSupportedRecorderVersion = "0.4.1" +const MinSupportedRecorderVersion = "0.4.1" // We currently support two formats, semantic version tag or image hash (sha256). // TODO: Consider deprecating tag version and switch to hash only. @@ -54,7 +54,7 @@ func (c ServiceConfig) IsValid() error { func RunnerIsValid(runner string) error { for _, re := range recorderRunnerREs { if matches := re.FindStringSubmatch(runner); len(matches) > 1 { - return checkMinVersion(minSupportedRecorderVersion, matches[1]) + return checkMinVersion(MinSupportedRecorderVersion, matches[1]) } } return fmt.Errorf("failed to validate runner") diff --git a/public/job/job_test.go b/public/job/job_test.go index b880c98..7c6b834 100644 --- a/public/job/job_test.go +++ b/public/job/job_test.go @@ -65,7 +65,7 @@ func TestJobConfigIsValid(t *testing.T) { name: "invalid max duration", cfg: Config{ Type: TypeRecording, - Runner: "mattermost/calls-recorder:v" + minSupportedRecorderVersion, + Runner: "mattermost/calls-recorder:v" + MinSupportedRecorderVersion, InputData: recorderCfg.ToMap(), MaxDurationSec: -1, }, @@ -78,13 +78,13 @@ func TestJobConfigIsValid(t *testing.T) { Runner: "mattermost/calls-recorder:v0.1.0", InputData: recorderCfg.ToMap(), }, - expectedError: fmt.Sprintf("invalid Runner value: actual version (0.1.0) is lower than minimum supported version (%s)", minSupportedRecorderVersion), + expectedError: fmt.Sprintf("invalid Runner value: actual version (0.1.0) is lower than minimum supported version (%s)", MinSupportedRecorderVersion), }, { name: "valid", cfg: Config{ Type: TypeRecording, - Runner: "mattermost/calls-recorder:v" + minSupportedRecorderVersion, + Runner: "mattermost/calls-recorder:v" + MinSupportedRecorderVersion, InputData: recorderCfg.ToMap(), MaxDurationSec: 60, }, @@ -93,7 +93,7 @@ func TestJobConfigIsValid(t *testing.T) { name: "valid daily", cfg: Config{ Type: TypeRecording, - Runner: "mattermost/calls-recorder-daily:v" + minSupportedRecorderVersion + "-dev", + Runner: "mattermost/calls-recorder-daily:v" + MinSupportedRecorderVersion + "-dev", InputData: recorderCfg.ToMap(), MaxDurationSec: 60, }, @@ -126,7 +126,7 @@ func TestServiceConfigIsValid(t *testing.T) { { name: "valid config", cfg: ServiceConfig{ - Runner: "mattermost/calls-recorder:v" + minSupportedRecorderVersion, + Runner: "mattermost/calls-recorder:v" + MinSupportedRecorderVersion, }, }, } diff --git a/service/auth.go b/service/auth.go index 0ac002d..47e7e44 100644 --- a/service/auth.go +++ b/service/auth.go @@ -10,6 +10,8 @@ import ( "net/http" "strings" + "github.com/mattermost/calls-offloader/service/auth" + "github.com/mattermost/mattermost/server/public/shared/mlog" ) @@ -48,6 +50,23 @@ func (s *Service) basicAuthHandler(r *http.Request) (string, int, error) { return "", http.StatusUnauthorized, errors.New("authentication failed: unauthorized") } + // If self registrations are enabled we attempt to automatically register the + // client upon the first authentication attempt. This ensures actions (e.g. starting a job) + // to be fully atomic. + if s.cfg.API.Security.AllowSelfRegistration && r.URL.Path != "/unregister" { + err := s.auth.Register(clientID, authKey) + if err != nil && err != auth.ErrAlreadyRegistered { + s.log.Error("failed to register client in auth handler", mlog.String("clientID", clientID), mlog.Err(err)) + return "", http.StatusUnauthorized, errors.New("unauthorized") + } + + if err == auth.ErrAlreadyRegistered { + s.log.Debug("client already registered, attempting authentication", mlog.String("clientID", clientID)) + } else { + s.log.Debug("client registered during auth", mlog.String("clientID", clientID)) + } + } + if err := s.auth.Authenticate(clientID, authKey); err != nil { s.log.Error("authentication failed", mlog.Err(err)) return "", http.StatusUnauthorized, errors.New("authentication failed") diff --git a/service/auth/service.go b/service/auth/service.go index 9a1cbe8..98290f2 100644 --- a/service/auth/service.go +++ b/service/auth/service.go @@ -12,6 +12,10 @@ import ( const MinKeyLen = 32 +var ( + ErrAlreadyRegistered = errors.New("registration failed: already registered") +) + type Service struct { sessionCache *SessionCache store store.Store @@ -47,7 +51,7 @@ func (s *Service) Register(id, key string) error { } if _, err := s.store.Get(id); err == nil { - return errors.New("registration failed: already registered") + return ErrAlreadyRegistered } else if err != nil && !errors.Is(err, store.ErrNotFound) { return fmt.Errorf("registration failed: %w", err) } @@ -58,7 +62,7 @@ func (s *Service) Register(id, key string) error { } if err := s.store.Put(id, hash); errors.Is(err, store.ErrConflict) { - return errors.New("registration failed: already registered") + return ErrAlreadyRegistered } else if err != nil { return fmt.Errorf("registration failed: %w", err) } diff --git a/service/client_test.go b/service/client_test.go index 2763aec..1babda3 100644 --- a/service/client_test.go +++ b/service/client_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/mattermost/calls-offloader/public" + "github.com/mattermost/calls-offloader/public/job" "github.com/mattermost/calls-offloader/service/auth" "github.com/mattermost/calls-offloader/service/random" @@ -260,3 +261,41 @@ func TestClientGetVersionInfo(t *testing.T) { require.NotEmpty(t, info) require.Equal(t, getVersionInfo(), info) } + +func TestClientAutoRegister(t *testing.T) { + th := SetupTestHelper(t, nil) + defer th.Teardown() + + clientID := "clientA" + + authKey, err := random.NewSecureString(auth.MinKeyLen) + require.NoError(t, err) + + c, err := public.NewClient(public.ClientConfig{ + URL: th.apiURL, + ClientID: clientID, + AuthKey: authKey, + }) + require.NoError(t, err) + require.NotNil(t, c) + defer c.Close() + + t.Run("unauthorized", func(t *testing.T) { + err = c.Init(job.ServiceConfig{ + Runner: "", + }) + require.EqualError(t, err, "unauthorized") + }) + + th.srvc.cfg.API.Security.AllowSelfRegistration = true + + t.Run("automatic registration", func(t *testing.T) { + err = c.Init(job.ServiceConfig{ + Runner: "mattermost/calls-recorder:v" + job.MinSupportedRecorderVersion, + }) + require.NoError(t, err) + + err := c.Login(clientID, authKey) + require.NoError(t, err) + }) +}