Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Move credential checks to Connector interface #3

Merged
merged 1 commit into from
Jun 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 10 additions & 1 deletion connector/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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,
Expand All @@ -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
}
Expand Down
6 changes: 6 additions & 0 deletions connector/dummy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ var (

func TestDummyConnector_ValidateCreate(t *testing.T) {
conn := NewDummy(
"username",
"password",
defaultPeriod,
defaultFlags,
defaultPermanentFlags,
Expand Down Expand Up @@ -67,6 +69,8 @@ func TestDummyConnector_ValidateCreate(t *testing.T) {

func TestDummyConnector_ValidateUpdate(t *testing.T) {
conn := NewDummy(
"username",
"password",
defaultPeriod,
defaultFlags,
defaultPermanentFlags,
Expand Down Expand Up @@ -97,6 +101,8 @@ func TestDummyConnector_ValidateUpdate(t *testing.T) {

func TestDummyConnector_ValidateDelete(t *testing.T) {
conn := NewDummy(
"username",
"password",
defaultPeriod,
defaultFlags,
defaultPermanentFlags,
Expand Down
12 changes: 7 additions & 5 deletions demo/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func main() {
server := gluon.New(filepath.Join(dir, "server"))

connector := connector.NewDummy(
"[email protected]",
"password",
time.Second,
imap.NewFlagSet(),
imap.NewFlagSet(),
Expand All @@ -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")
Expand Down
3 changes: 1 addition & 2 deletions events/user.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package events

type EventUserAdded struct {
UserID string
Username string
UserID string
}

func (EventUserAdded) _isEvent() {}
Expand Down
13 changes: 8 additions & 5 deletions internal/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions internal/remote/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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
}
Expand All @@ -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
}
}
Expand Down
8 changes: 2 additions & 6 deletions internal/remote/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
Expand Down
14 changes: 7 additions & 7 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 9 additions & 9 deletions tests/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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())))
}