diff --git a/connector/connector.go b/connector/connector.go index 2cfc7191..fc238bc5 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -9,6 +9,9 @@ import ( // Connector connects the gluon server to a remote mail store. type Connector interface { + // Authorize returns whether the given username/password combination are valid for this connector. + Authorize(username, password string) bool + // GetUpdates returns a stream of updates that the gluon server should apply. GetUpdates() <-chan imap.Update diff --git a/connector/dummy.go b/connector/dummy.go index aa3dc4ef..14da24e2 100644 --- a/connector/dummy.go +++ b/connector/dummy.go @@ -21,6 +21,9 @@ type Dummy struct { // state holds the fake connector state. state *dummyState + // username and password are the credentials for this connector. + username, password string + // These hold the default flags/attributes given to mailboxes. flags, permFlags, attrs imap.FlagSet @@ -38,9 +41,11 @@ type Dummy struct { queueLock sync.Mutex } -func NewDummy(period time.Duration, flags, permFlags, attrs imap.FlagSet) *Dummy { +func NewDummy(username, password string, period time.Duration, flags, permFlags, attrs imap.FlagSet) *Dummy { conn := &Dummy{ state: newDummyState(flags, permFlags, attrs), + username: username, + password: password, flags: flags, permFlags: permFlags, attrs: attrs, @@ -62,6 +67,10 @@ func NewDummy(period time.Duration, flags, permFlags, attrs imap.FlagSet) *Dummy return conn } +func (conn *Dummy) Authorize(username, password string) bool { + return username == conn.username && password == conn.password +} + func (conn *Dummy) GetUpdates() <-chan imap.Update { return conn.updateCh } diff --git a/connector/dummy_test.go b/connector/dummy_test.go index 20f3b197..453e610d 100644 --- a/connector/dummy_test.go +++ b/connector/dummy_test.go @@ -18,6 +18,8 @@ var ( func TestDummyConnector_ValidateCreate(t *testing.T) { conn := NewDummy( + "username", + "password", defaultPeriod, defaultFlags, defaultPermanentFlags, @@ -67,6 +69,8 @@ func TestDummyConnector_ValidateCreate(t *testing.T) { func TestDummyConnector_ValidateUpdate(t *testing.T) { conn := NewDummy( + "username", + "password", defaultPeriod, defaultFlags, defaultPermanentFlags, @@ -97,6 +101,8 @@ func TestDummyConnector_ValidateUpdate(t *testing.T) { func TestDummyConnector_ValidateDelete(t *testing.T) { conn := NewDummy( + "username", + "password", defaultPeriod, defaultFlags, defaultPermanentFlags, diff --git a/demo/demo.go b/demo/demo.go index bb1931bc..c91c95b0 100644 --- a/demo/demo.go +++ b/demo/demo.go @@ -26,6 +26,8 @@ func main() { server := gluon.New(filepath.Join(dir, "server")) connector := connector.NewDummy( + "user@example.com", + "password", time.Second, imap.NewFlagSet(), imap.NewFlagSet(), @@ -37,18 +39,18 @@ func main() { logrus.WithError(err).Fatal("Failed to create store") } - if err := server.AddUser( - "userID", - "username", - "password", + userID, err := server.AddUser( connector, store, dialect.SQLite, fmt.Sprintf("file:%v?cache=shared&_fk=1", filepath.Join(dir, fmt.Sprintf("%v.db", "userID"))), - ); err != nil { + ) + if err != nil { logrus.WithError(err).Fatal("Failed to add user") } + logrus.WithField("userID", userID).Info("User added to server") + listener, err := net.Listen("tcp", ":1143") if err != nil { logrus.WithError(err).Fatal("Failed to listen") diff --git a/events/user.go b/events/user.go index 1c8d82ae..93431711 100644 --- a/events/user.go +++ b/events/user.go @@ -1,8 +1,7 @@ package events type EventUserAdded struct { - UserID string - Username string + UserID string } func (EventUserAdded) _isEvent() {} diff --git a/internal/backend/backend.go b/internal/backend/backend.go index c12a020b..4c08f130 100644 --- a/internal/backend/backend.go +++ b/internal/backend/backend.go @@ -9,6 +9,7 @@ import ( "github.com/ProtonMail/gluon/internal/backend/ent" "github.com/ProtonMail/gluon/internal/remote" "github.com/ProtonMail/gluon/store" + "github.com/google/uuid" "github.com/sirupsen/logrus" ) @@ -36,23 +37,25 @@ func (b *Backend) SetDelimiter(delim string) { b.delim = delim } -func (b *Backend) AddUser(userID, username, password string, conn connector.Connector, store store.Store, client *ent.Client) error { +func (b *Backend) AddUser(conn connector.Connector, store store.Store, client *ent.Client) (string, error) { b.usersLock.Lock() defer b.usersLock.Unlock() - remote, err := b.remote.AddUser(userID, username, password, conn) + userID := uuid.NewString() + + remote, err := b.remote.AddUser(userID, conn) if err != nil { - return err + return "", err } user, err := newUser(userID, client, remote, store, b.delim) if err != nil { - return err + return "", err } b.users[userID] = user - return nil + return userID, nil } func (b *Backend) GetState(username, password string) (*State, error) { diff --git a/internal/remote/manager.go b/internal/remote/manager.go index 28f33ef4..3215d5eb 100644 --- a/internal/remote/manager.go +++ b/internal/remote/manager.go @@ -32,7 +32,7 @@ func New(dir string) *Manager { // AddUser adds the remote user with the given (IMAP) credentials to the remote manager. // The user interacts with the remote via the given connector. -func (m *Manager) AddUser(userID, username, password string, conn connector.Connector) (*User, error) { +func (m *Manager) AddUser(userID string, conn connector.Connector) (*User, error) { m.usersLock.Lock() defer m.usersLock.Unlock() @@ -41,7 +41,7 @@ func (m *Manager) AddUser(userID, username, password string, conn connector.Conn return nil, err } - user, err := newUser(userID, username, password, path, conn) + user, err := newUser(userID, path, conn) if err != nil { return nil, err } @@ -57,7 +57,7 @@ func (m *Manager) GetUserID(username, password string) (string, error) { defer m.usersLock.Unlock() for _, user := range m.users { - if user.username == username && user.password == password { + if user.conn.Authorize(username, password) { return user.userID, nil } } diff --git a/internal/remote/user.go b/internal/remote/user.go index dbcec03b..8157bf4c 100644 --- a/internal/remote/user.go +++ b/internal/remote/user.go @@ -13,9 +13,7 @@ import ( // User performs operations against a remote server using a connector. type User struct { - userID string - username string - password string + userID string // path is the path at which the operation queue will be saved to disk. path string @@ -40,11 +38,9 @@ type User struct { // newUser constructs a new user with the given (IMAP) credentials. // It serializes its operation queue to a file at the given filepath, // and performs remote operations using the given connector. -func newUser(userID, username, password, path string, conn connector.Connector) (*User, error) { +func newUser(userID, path string, conn connector.Connector) (*User, error) { user := &User{ userID: userID, - username: username, - password: password, path: path, conn: conn, updatesCh: make(chan imap.Update), diff --git a/server.go b/server.go index ecc82128..ac20e98e 100644 --- a/server.go +++ b/server.go @@ -65,22 +65,22 @@ func New(dir string, withOpt ...Option) *Server { } // AddUser makes a user available to the mailserver. -func (s *Server) AddUser(userID, username, password string, conn connector.Connector, store store.Store, driver, source string) error { +func (s *Server) AddUser(conn connector.Connector, store store.Store, driver, source string) (string, error) { client, err := ent.Open(driver, source) if err != nil { - return err + return "", err } - if err := s.backend.AddUser(userID, username, password, conn, store, client); err != nil { - return err + userID, err := s.backend.AddUser(conn, store, client) + if err != nil { + return "", err } s.publish(events.EventUserAdded{ - UserID: userID, - Username: username, + UserID: userID, }) - return nil + return userID, nil } // AddWatcher adds a new watcher. diff --git a/tests/server_test.go b/tests/server_test.go index 585d2e2b..9ba921f4 100644 --- a/tests/server_test.go +++ b/tests/server_test.go @@ -13,7 +13,6 @@ import ( "github.com/ProtonMail/gluon" "github.com/ProtonMail/gluon/connector" "github.com/ProtonMail/gluon/imap" - "github.com/ProtonMail/gluon/internal/utils" "github.com/ProtonMail/gluon/store" "github.com/emersion/go-imap/client" "github.com/google/uuid" @@ -50,24 +49,25 @@ func runServer(tb testing.TB, credentials map[string]string, delim string, tests userIDs := make(map[string]string) conns := make(map[string]Connector) - for user, pass := range credentials { - userID := utils.NewRandomUserID() - + for username, password := range credentials { conn := connector.NewDummy( + username, + password, defaultPeriod, defaultFlags, defaultPermanentFlags, defaultAttributes, ) - store, err := store.NewOnDiskStore(tb.TempDir(), []byte("passphrase")) + store, err := store.NewOnDiskStore(tb.TempDir(), []byte(password)) require.NoError(tb, err) - require.NoError(tb, server.AddUser(userID, user, pass, conn, store, dialect.SQLite, getEntPath(tb.TempDir(), userID))) + userID, err := server.AddUser(conn, store, dialect.SQLite, getEntPath(tb.TempDir())) + require.NoError(tb, err) require.NoError(tb, conn.Sync(ctx)) - userIDs[user] = userID + userIDs[username] = userID conns[userID] = conn } @@ -125,6 +125,6 @@ func withData(s *testSession, username string, tests func(string, string)) { tests(mbox, mboxID) } -func getEntPath(dir, userID string) string { - return fmt.Sprintf("file:%v?cache=shared&_fk=1", filepath.Join(dir, fmt.Sprintf("%v.db", userID))) +func getEntPath(dir string) string { + return fmt.Sprintf("file:%v?cache=shared&_fk=1", filepath.Join(dir, fmt.Sprintf("%v.db", uuid.NewString()))) }