From cb22ae9e4127e854ed753705d8bc48adbbe88bf7 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 9 Dec 2019 15:24:42 +0100 Subject: [PATCH 1/2] orchestra: introduce the statefile For now we only use an in memory statefile, but it's clear that later we'll need to persist it on disk to keep information required to register and log-in with the OONI orchestra. Part of #148. --- internal/orchestra/statefile/statefile.go | 81 ++++++++++++++++ .../orchestra/statefile/statefile_test.go | 94 +++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 internal/orchestra/statefile/statefile.go create mode 100644 internal/orchestra/statefile/statefile_test.go diff --git a/internal/orchestra/statefile/statefile.go b/internal/orchestra/statefile/statefile.go new file mode 100644 index 00000000..e92530c8 --- /dev/null +++ b/internal/orchestra/statefile/statefile.go @@ -0,0 +1,81 @@ +// Package statefile defines the state file +package statefile + +import ( + "errors" + "sync" + "time" + + "github.com/ooni/probe-engine/internal/orchestra/login" +) + +// State is the state stored inside the state file +type State struct { + ClientID string + Expire time.Time + Password string + Token string +} + +// Auth returns an authentication structure, if possible, otherwise +// it returns nil, meaning that you should login again. +func (s State) Auth() *login.Auth { + if s.Token == "" { + return nil + } + if time.Now().Add(30 * time.Second).After(s.Expire) { + return nil + } + return &login.Auth{ + Expire: s.Expire, + Token: s.Token, + } +} + +// Credentials returns login credentials, if possible, otherwise it +// returns nil, meaning that you should create an account. +func (s State) Credentials() *login.Credentials { + if s.ClientID == "" { + return nil + } + if s.Password == "" { + return nil + } + return &login.Credentials{ + ClientID: s.ClientID, + Password: s.Password, + } +} + +// StateFile is a generic state file +type StateFile interface { + Set(*State) error + Get() (*State, error) +} + +type memory struct { + state State + mu sync.Mutex +} + +// NewMemory creates a new state file in memory +func NewMemory(workdir string) StateFile { + return &memory{} +} + +func (sf *memory) Set(s *State) error { + if s == nil { + return errors.New("passed nil pointer") + } + sf.mu.Lock() + defer sf.mu.Unlock() + sf.state = *s + return nil +} + +func (sf *memory) Get() (*State, error) { + sf.mu.Lock() + defer sf.mu.Unlock() + state := sf.state + return &state, nil +} diff --git a/internal/orchestra/statefile/statefile_test.go b/internal/orchestra/statefile/statefile_test.go new file mode 100644 index 00000000..ddc48767 --- /dev/null +++ b/internal/orchestra/statefile/statefile_test.go @@ -0,0 +1,94 @@ +package statefile + +import ( + "testing" + "time" +) + +func TestUnitStateAuth(t *testing.T) { + t.Run("with no Token", func(t *testing.T) { + state := State{Expire: time.Now().Add(10 * time.Hour)} + if state.Auth() != nil { + t.Fatal("expected nil here") + } + }) + t.Run("with expired Token", func(t *testing.T) { + state := State{ + Expire: time.Now().Add(-1 * time.Hour), + Token: "xx-x-xxx-xx", + } + if state.Auth() != nil { + t.Fatal("expected nil here") + } + }) + t.Run("with good Token", func(t *testing.T) { + state := State{ + Expire: time.Now().Add(10 * time.Hour), + Token: "xx-x-xxx-xx", + } + if state.Auth() == nil { + t.Fatal("expected valid auth here") + } + }) +} + +func TestUnitStateCredentials(t *testing.T) { + t.Run("with no ClientID", func(t *testing.T) { + state := State{} + if state.Credentials() != nil { + t.Fatal("expected nil here") + } + }) + t.Run("with no Password", func(t *testing.T) { + state := State{ + ClientID: "xx-x-xxx-xx", + } + if state.Credentials() != nil { + t.Fatal("expected nil here") + } + }) + t.Run("with all good", func(t *testing.T) { + state := State{ + ClientID: "xx-x-xxx-xx", + Password: "xx", + } + if state.Credentials() == nil { + t.Fatal("expected valid auth here") + } + }) +} + +func TestUnitStateFileMemory(t *testing.T) { + sf := NewMemory("/tmp") + if sf == nil { + t.Fatal("expected non nil pointer here") + } + if err := sf.Set(nil); err == nil { + t.Fatal("expected an error here") + } + s := State{ + Expire: time.Now(), + Password: "xy", + Token: "abc", + ClientID: "xx", + } + if err := sf.Set(&s); err != nil { + t.Fatal(err) + } + os, err := sf.Get() + if err != nil { + t.Fatal(err) + } + if s.ClientID != os.ClientID { + t.Fatal("the ClientID field has changed") + } + if !s.Expire.Equal(os.Expire) { + t.Fatal("the Expire field has changed") + } + if s.Password != os.Password { + t.Fatal("the Password field has changed") + } + if s.Token != os.Token { + t.Fatal("the Token field has changed") + } +} From 0f73b19dd92c7434e70b6fb91252a7e26c763ccb Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 9 Dec 2019 15:28:48 +0100 Subject: [PATCH 2/2] oops --- internal/orchestra/login/login.go | 22 ++++++++----------- .../orchestra/testorchestra/testorchestra.go | 8 ++++--- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/internal/orchestra/login/login.go b/internal/orchestra/login/login.go index 056bfb63..f2a95529 100644 --- a/internal/orchestra/login/login.go +++ b/internal/orchestra/login/login.go @@ -12,16 +12,16 @@ import ( // Config contains configs for logging in with the OONI orchestra. type Config struct { - BaseURL string - ClientID string - HTTPClient *http.Client - Logger log.Logger - Password string - UserAgent string + BaseURL string + Credentials Credentials + HTTPClient *http.Client + Logger log.Logger + UserAgent string } -type request struct { - Username string `json:"username"` +// Credentials contains the login credentials +type Credentials struct { + ClientID string `json:"username"` Password string `json:"password"` } @@ -33,17 +33,13 @@ type Auth struct { // Do logs this probe in with OONI orchestra func Do(ctx context.Context, config Config) (*Auth, error) { - req := &request{ - Password: config.Password, - Username: config.ClientID, - } var resp Auth err := (&jsonapi.Client{ BaseURL: config.BaseURL, HTTPClient: config.HTTPClient, Logger: config.Logger, UserAgent: config.UserAgent, - }).Create(ctx, "/api/v1/login", req, &resp) + }).Create(ctx, "/api/v1/login", config.Credentials, &resp) if err != nil { return nil, err } diff --git a/internal/orchestra/testorchestra/testorchestra.go b/internal/orchestra/testorchestra/testorchestra.go index cbb8af3a..7e243d60 100644 --- a/internal/orchestra/testorchestra/testorchestra.go +++ b/internal/orchestra/testorchestra/testorchestra.go @@ -35,11 +35,13 @@ func Register() (string, error) { // information on success, and an error on failure. func Login(clientID string) (*login.Auth, error) { return login.Do(context.Background(), login.Config{ - BaseURL: "https://ps-test.ooni.io", - ClientID: clientID, + BaseURL: "https://ps-test.ooni.io", + Credentials: login.Credentials{ + ClientID: clientID, + Password: password, + }, HTTPClient: http.DefaultClient, Logger: log.Log, - Password: password, UserAgent: "miniooni/0.1.0-dev", }) }