Skip to content

Commit

Permalink
feat: Support aliases in dummy connector (#5)
Browse files Browse the repository at this point in the history
Aliases are different usernames which are associated with the same
backend account. For instance, a user might have two aliases on their
account, `[email protected]` and `[email protected]`. In this case, their
IMAP client would be able to login with either of these addresses as the
username and it would connect to the same backend IMAP account.

This feature allows Bridge to support Proton accounts with multiple
addresses.
  • Loading branch information
jameshoulahan committed Jun 7, 2022
1 parent 2ec2361 commit f9a3ea4
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 24 deletions.
18 changes: 13 additions & 5 deletions connector/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/internal/ticker"
"github.com/bradenaw/juniper/xslices"
"golang.org/x/exp/slices"
)

var (
Expand All @@ -21,8 +22,11 @@ type Dummy struct {
// state holds the fake connector state.
state *dummyState

// username and password are the credentials for this connector.
username, password string
// usernames holds usernames that can be used for authorization.
usernames []string

// password holds the password that can be used for authorization.
password string

// These hold the default flags/attributes given to mailboxes.
flags, permFlags, attrs imap.FlagSet
Expand All @@ -41,10 +45,10 @@ type Dummy struct {
queueLock sync.Mutex
}

func NewDummy(username, password string, period time.Duration, flags, permFlags, attrs imap.FlagSet) *Dummy {
func NewDummy(usernames []string, password string, period time.Duration, flags, permFlags, attrs imap.FlagSet) *Dummy {
conn := &Dummy{
state: newDummyState(flags, permFlags, attrs),
username: username,
usernames: usernames,
password: password,
flags: flags,
permFlags: permFlags,
Expand All @@ -68,7 +72,11 @@ func NewDummy(username, password string, period time.Duration, flags, permFlags,
}

func (conn *Dummy) Authorize(username, password string) bool {
return username == conn.username && password == conn.password
if password != conn.password {
return false
}

return slices.Contains(conn.usernames, username)
}

func (conn *Dummy) GetUpdates() <-chan imap.Update {
Expand Down
6 changes: 3 additions & 3 deletions connector/dummy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var (

func TestDummyConnector_ValidateCreate(t *testing.T) {
conn := NewDummy(
"username",
[]string{"username"},
"password",
defaultPeriod,
defaultFlags,
Expand Down Expand Up @@ -69,7 +69,7 @@ func TestDummyConnector_ValidateCreate(t *testing.T) {

func TestDummyConnector_ValidateUpdate(t *testing.T) {
conn := NewDummy(
"username",
[]string{"username"},
"password",
defaultPeriod,
defaultFlags,
Expand Down Expand Up @@ -101,7 +101,7 @@ func TestDummyConnector_ValidateUpdate(t *testing.T) {

func TestDummyConnector_ValidateDelete(t *testing.T) {
conn := NewDummy(
"username",
[]string{"username"},
"password",
defaultPeriod,
defaultFlags,
Expand Down
2 changes: 1 addition & 1 deletion demo/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func main() {
server := gluon.New(filepath.Join(dir, "server"))

connector := connector.NewDummy(
"[email protected]",
[]string{"[email protected]", "[email protected]"},
"password",
time.Second,
imap.NewFlagSet(),
Expand Down
24 changes: 23 additions & 1 deletion tests/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ func TestLoginLiteral(t *testing.T) {
}

func TestLoginMultiple(t *testing.T) {
runTest(t, map[string]string{"user1": "pass1", "user2": "pass2"}, "/", []int{1, 2}, func(c map[int]*testConnection, _ *testSession) {
runTest(t, []credentials{
{usernames: []string{"user1"}, password: "pass1"},
{usernames: []string{"user2"}, password: "pass2"},
}, "/", []int{1, 2}, func(c map[int]*testConnection, _ *testSession) {
// Login as the first user.
c[1].C("A001 login user1 pass1").OK("A001")

Expand All @@ -43,6 +46,25 @@ func TestLoginMultiple(t *testing.T) {
})
}

func TestLoginAlias(t *testing.T) {
runTest(t, []credentials{{
usernames: []string{"alias1", "alias2"},
password: "pass",
}}, "/", []int{1, 2}, func(c map[int]*testConnection, _ *testSession) {
// Login as each alias.
c[1].C("tag1 login alias1 pass").OK("tag1")
c[2].C("tag2 login alias2 pass").OK("tag2")

// Create a message with each alias.
c[1].C("tag3 append inbox {11}\r\nTo: [email protected]").OK("tag3")
c[2].C("tag4 append inbox {11}\r\nTo: [email protected]").OK("tag4")

// Both messages should be visible to both clients.
c[1].C("tag5 status inbox (messages)").Sx("MESSAGES 2").OK("tag5")
c[2].C("tag6 status inbox (messages)").Sx("MESSAGES 2").OK("tag6")
})
}

func TestLoginFailure(t *testing.T) {
runOneToOneTest(t, "user", "pass", "/", func(c *testConnection, _ *testSession) {
c.C("A001 login baduser badpass").NO("A001")
Expand Down
5 changes: 4 additions & 1 deletion tests/multi_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import (
)

func TestMultiUser(t *testing.T) {
runTest(t, map[string]string{"user1": "pass", "user2": "pass"}, "/", []int{1, 2}, func(c map[int]*testConnection, s *testSession) {
runTest(t, []credentials{
{usernames: []string{"user1"}, password: "pass"},
{usernames: []string{"user2"}, password: "pass"},
}, "/", []int{1, 2}, func(c map[int]*testConnection, s *testSession) {
c[1].C(`A001 login user1 pass`).OK(`A001`)
c[2].C(`B001 login user2 pass`).OK(`B001`)

Expand Down
23 changes: 16 additions & 7 deletions tests/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import (

// runOneToOneTest runs a test with one account and one connection.
func runOneToOneTest(tb testing.TB, username, password, delimiter string, tests func(*testConnection, *testSession)) {
runTest(tb, map[string]string{username: password}, delimiter, []int{1}, func(c map[int]*testConnection, s *testSession) {
runTest(tb, []credentials{{
usernames: []string{username},
password: password,
}}, delimiter, []int{1}, func(c map[int]*testConnection, s *testSession) {
tests(c[1], s)
})
}
Expand All @@ -39,7 +42,10 @@ func runOneToOneTestWithData(tb testing.TB, username, password, delimiter string

// runManyToOneTest runs a test with one account and multiple connections.
func runManyToOneTest(tb testing.TB, username, password, delimiter string, connIDs []int, tests func(map[int]*testConnection, *testSession)) {
runTest(tb, map[string]string{username: password}, delimiter, connIDs, func(c map[int]*testConnection, s *testSession) {
runTest(tb, []credentials{{
usernames: []string{username},
password: password,
}}, delimiter, connIDs, func(c map[int]*testConnection, s *testSession) {
tests(c, s)
})
}
Expand Down Expand Up @@ -69,8 +75,8 @@ func runManyToOneTestWithData(tb testing.TB, username, password, delimiter strin
}

// runTest runs the mailserver and creates test connections to it.
func runTest(tb testing.TB, credentials map[string]string, delimiter string, connIDs []int, tests func(map[int]*testConnection, *testSession)) {
runServer(tb, credentials, delimiter, func(s *testSession) {
func runTest(tb testing.TB, creds []credentials, delimiter string, connIDs []int, tests func(map[int]*testConnection, *testSession)) {
runServer(tb, creds, delimiter, func(s *testSession) {
withConnections(tb, s, connIDs, func(c map[int]*testConnection) {
tests(c, s)
})
Expand All @@ -80,8 +86,8 @@ func runTest(tb testing.TB, credentials map[string]string, delimiter string, con
// -- IMap client test helpers

// runTestClient runs the mailserver and creates test connections to it using an imap client.
func runTestClient(tb testing.TB, credentials map[string]string, delimiter string, connIDs []int, tests func(map[int]*client.Client, *testSession)) {
runServer(tb, credentials, delimiter, func(s *testSession) {
func runTestClient(tb testing.TB, creds []credentials, delimiter string, connIDs []int, tests func(map[int]*client.Client, *testSession)) {
runServer(tb, creds, delimiter, func(s *testSession) {
withClients(tb, s, connIDs, func(clientMap map[int]*client.Client) {
tests(clientMap, s)
})
Expand All @@ -90,7 +96,10 @@ func runTestClient(tb testing.TB, credentials map[string]string, delimiter strin

// runOneToOneTestClient runs a test with one account and one connection using an imap client.
func runOneToOneTestClient(tb testing.TB, username, password, delimiter string, test func(*client.Client, *testSession)) {
runTestClient(tb, map[string]string{username: password}, delimiter, []int{1}, func(c map[int]*client.Client, s *testSession) {
runTestClient(tb, []credentials{{
usernames: []string{username},
password: password,
}}, delimiter, []int{1}, func(c map[int]*client.Client, s *testSession) {
test(c[1], s)
})
}
Expand Down
20 changes: 14 additions & 6 deletions tests/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ var (
defaultAttributes = imap.NewFlagSet()
)

type credentials struct {
usernames []string
password string
}

// runServer initializes and starts the mailserver.
func runServer(tb testing.TB, credentials map[string]string, delim string, tests func(*testSession)) {
func runServer(tb testing.TB, creds []credentials, delim string, tests func(*testSession)) {
server := gluon.New(
tb.TempDir(),
gluon.WithDelimiter(delim),
Expand All @@ -49,25 +54,28 @@ func runServer(tb testing.TB, credentials map[string]string, delim string, tests
userIDs := make(map[string]string)
conns := make(map[string]Connector)

for username, password := range credentials {
for _, creds := range creds {
conn := connector.NewDummy(
username,
password,
creds.usernames,
creds.password,
defaultPeriod,
defaultFlags,
defaultPermanentFlags,
defaultAttributes,
)

store, err := store.NewOnDiskStore(tb.TempDir(), []byte(password))
store, err := store.NewOnDiskStore(tb.TempDir(), []byte(creds.password))
require.NoError(tb, err)

userID, err := server.AddUser(conn, store, dialect.SQLite, getEntPath(tb.TempDir()))
require.NoError(tb, err)

require.NoError(tb, conn.Sync(ctx))

userIDs[username] = userID
for _, username := range creds.usernames {
userIDs[username] = userID
}

conns[userID] = conn
}

Expand Down

0 comments on commit f9a3ea4

Please sign in to comment.