mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-17 23:56:56 +00:00
Renamed bridge to general users and keep bridge only for bridge stuff
This commit is contained in:
136
internal/users/credentials/credentials.go
Normal file
136
internal/users/credentials/credentials.go
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package credentials implements our struct stored in keychain.
|
||||
// Store struct is kind of like a database client.
|
||||
// Credentials struct is kind of like one record from the database.
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const sep = "\x00"
|
||||
|
||||
var (
|
||||
log = logrus.WithField("pkg", "credentials") //nolint[gochecknoglobals]
|
||||
|
||||
ErrWrongFormat = errors.New("backend/creds: malformed password")
|
||||
)
|
||||
|
||||
type Credentials struct {
|
||||
UserID, // Do not marshal; used as a key.
|
||||
Name,
|
||||
Emails,
|
||||
APIToken,
|
||||
MailboxPassword,
|
||||
BridgePassword,
|
||||
Version string
|
||||
Timestamp int64
|
||||
IsHidden, // Deprecated.
|
||||
IsCombinedAddressMode bool
|
||||
}
|
||||
|
||||
func (s *Credentials) Marshal() string {
|
||||
items := []string{
|
||||
s.Name, // 0
|
||||
s.Emails, // 1
|
||||
s.APIToken, // 2
|
||||
s.MailboxPassword, // 3
|
||||
s.BridgePassword, // 4
|
||||
s.Version, // 5
|
||||
"", // 6
|
||||
"", // 7
|
||||
"", // 8
|
||||
}
|
||||
|
||||
items[6] = fmt.Sprint(s.Timestamp)
|
||||
|
||||
if s.IsHidden {
|
||||
items[7] = "1"
|
||||
}
|
||||
|
||||
if s.IsCombinedAddressMode {
|
||||
items[8] = "1"
|
||||
}
|
||||
|
||||
str := strings.Join(items, sep)
|
||||
return base64.StdEncoding.EncodeToString([]byte(str))
|
||||
}
|
||||
|
||||
func (s *Credentials) Unmarshal(secret string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
items := strings.Split(string(b), sep)
|
||||
|
||||
if len(items) != 9 {
|
||||
return ErrWrongFormat
|
||||
}
|
||||
|
||||
s.Name = items[0]
|
||||
s.Emails = items[1]
|
||||
s.APIToken = items[2]
|
||||
s.MailboxPassword = items[3]
|
||||
s.BridgePassword = items[4]
|
||||
s.Version = items[5]
|
||||
if _, err = fmt.Sscan(items[6], &s.Timestamp); err != nil {
|
||||
s.Timestamp = 0
|
||||
}
|
||||
if s.IsHidden = false; items[7] == "1" {
|
||||
s.IsHidden = true
|
||||
}
|
||||
if s.IsCombinedAddressMode = false; items[8] == "1" {
|
||||
s.IsCombinedAddressMode = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Credentials) SetEmailList(list []string) {
|
||||
s.Emails = strings.Join(list, ";")
|
||||
}
|
||||
|
||||
func (s *Credentials) EmailList() []string {
|
||||
return strings.Split(s.Emails, ";")
|
||||
}
|
||||
|
||||
func (s *Credentials) CheckPassword(password string) error {
|
||||
if subtle.ConstantTimeCompare([]byte(s.BridgePassword), []byte(password)) != 1 {
|
||||
log.WithFields(logrus.Fields{
|
||||
"userID": s.UserID,
|
||||
}).Debug("Incorrect bridge password")
|
||||
|
||||
return fmt.Errorf("backend/credentials: incorrect password")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Credentials) Logout() {
|
||||
s.APIToken = ""
|
||||
s.MailboxPassword = ""
|
||||
}
|
||||
|
||||
func (s *Credentials) IsConnected() bool {
|
||||
return s.APIToken != "" && s.MailboxPassword != ""
|
||||
}
|
||||
39
internal/users/credentials/crypto.go
Normal file
39
internal/users/credentials/crypto.go
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
)
|
||||
|
||||
const keySize = 16
|
||||
|
||||
// generateKey generates a new random key.
|
||||
func generateKey() []byte {
|
||||
key := make([]byte, keySize)
|
||||
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func generatePassword() string {
|
||||
return base64.RawURLEncoding.EncodeToString(generateKey())
|
||||
}
|
||||
330
internal/users/credentials/store.go
Normal file
330
internal/users/credentials/store.go
Normal file
@ -0,0 +1,330 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var storeLocker = sync.RWMutex{} //nolint[gochecknoglobals]
|
||||
|
||||
// Store is an encrypted credentials store.
|
||||
type Store struct {
|
||||
secrets *keychain.Access
|
||||
}
|
||||
|
||||
// NewStore creates a new encrypted credentials store.
|
||||
func NewStore(appName string) (*Store, error) {
|
||||
secrets, err := keychain.NewAccess(appName)
|
||||
return &Store{
|
||||
secrets: secrets,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (s *Store) Add(userID, userName, apiToken, mailboxPassword string, emails []string) (creds *Credentials, err error) {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"user": userID,
|
||||
"username": userName,
|
||||
"emails": emails,
|
||||
}).Trace("Adding new credentials")
|
||||
|
||||
if err = s.checkKeychain(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
creds = &Credentials{
|
||||
UserID: userID,
|
||||
Name: userName,
|
||||
APIToken: apiToken,
|
||||
MailboxPassword: mailboxPassword,
|
||||
IsHidden: false,
|
||||
}
|
||||
|
||||
creds.SetEmailList(emails)
|
||||
|
||||
var has bool
|
||||
if has, err = s.has(userID); err != nil {
|
||||
log.WithField("userID", userID).WithError(err).Error("Could not check if user credentials already exist")
|
||||
return
|
||||
}
|
||||
|
||||
if has {
|
||||
log.Info("Updating credentials of existing user")
|
||||
currentCredentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds.BridgePassword = currentCredentials.BridgePassword
|
||||
creds.IsCombinedAddressMode = currentCredentials.IsCombinedAddressMode
|
||||
creds.Timestamp = currentCredentials.Timestamp
|
||||
} else {
|
||||
log.Info("Generating credentials for new user")
|
||||
creds.BridgePassword = generatePassword()
|
||||
creds.IsCombinedAddressMode = true
|
||||
creds.Timestamp = time.Now().Unix()
|
||||
}
|
||||
|
||||
if err = s.saveCredentials(creds); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return creds, err
|
||||
}
|
||||
|
||||
func (s *Store) SwitchAddressMode(userID string) error {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
credentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
credentials.IsCombinedAddressMode = !credentials.IsCombinedAddressMode
|
||||
credentials.BridgePassword = generatePassword()
|
||||
|
||||
return s.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateEmails(userID string, emails []string) error {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
credentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
credentials.SetEmailList(emails)
|
||||
|
||||
return s.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
func (s *Store) UpdatePassword(userID, password string) error {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
credentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
credentials.MailboxPassword = password
|
||||
|
||||
return s.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateToken(userID, apiToken string) error {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
credentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
credentials.APIToken = apiToken
|
||||
|
||||
return s.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
func (s *Store) Logout(userID string) error {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
credentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
credentials.Logout()
|
||||
|
||||
return s.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
// List returns a list of usernames that have credentials stored.
|
||||
func (s *Store) List() (userIDs []string, err error) {
|
||||
storeLocker.RLock()
|
||||
defer storeLocker.RUnlock()
|
||||
|
||||
log.Trace("Listing credentials in credentials store")
|
||||
|
||||
if err = s.checkKeychain(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var allUserIDs []string
|
||||
if allUserIDs, err = s.secrets.List(); err != nil {
|
||||
log.WithError(err).Error("Could not list credentials")
|
||||
return
|
||||
}
|
||||
|
||||
credentialList := []*Credentials{}
|
||||
for _, userID := range allUserIDs {
|
||||
creds, getErr := s.get(userID)
|
||||
if getErr != nil {
|
||||
log.WithField("userID", userID).WithError(getErr).Warn("Failed to get credentials")
|
||||
continue
|
||||
}
|
||||
|
||||
if creds.Timestamp == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
credentialList = append(credentialList, creds)
|
||||
}
|
||||
|
||||
sort.Slice(credentialList, func(i, j int) bool {
|
||||
return credentialList[i].Timestamp < credentialList[j].Timestamp
|
||||
})
|
||||
|
||||
for _, credentials := range credentialList {
|
||||
userIDs = append(userIDs, credentials.UserID)
|
||||
}
|
||||
|
||||
return userIDs, err
|
||||
}
|
||||
|
||||
func (s *Store) GetAndCheckPassword(userID, password string) (creds *Credentials, err error) {
|
||||
storeLocker.RLock()
|
||||
defer storeLocker.RUnlock()
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"userID": userID,
|
||||
}).Debug("Checking bridge password")
|
||||
|
||||
credentials, err := s.Get(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := credentials.CheckPassword(password); err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"userID": userID,
|
||||
"err": err,
|
||||
}).Debug("Incorrect bridge password")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
func (s *Store) Get(userID string) (creds *Credentials, err error) {
|
||||
storeLocker.RLock()
|
||||
defer storeLocker.RUnlock()
|
||||
|
||||
var has bool
|
||||
if has, err = s.has(userID); err != nil {
|
||||
log.WithError(err).Error("Could not check for credentials")
|
||||
return
|
||||
}
|
||||
|
||||
if !has {
|
||||
err = errors.New("no credentials found for given userID")
|
||||
return
|
||||
}
|
||||
|
||||
return s.get(userID)
|
||||
}
|
||||
|
||||
func (s *Store) has(userID string) (has bool, err error) {
|
||||
if err = s.checkKeychain(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ids []string
|
||||
if ids, err = s.secrets.List(); err != nil {
|
||||
log.WithError(err).Error("Could not list credentials")
|
||||
return
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
if id == userID {
|
||||
has = true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Store) get(userID string) (creds *Credentials, err error) {
|
||||
log := log.WithField("user", userID)
|
||||
|
||||
if err = s.checkKeychain(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
secret, err := s.secrets.Get(userID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get credentials from native keychain")
|
||||
return
|
||||
}
|
||||
|
||||
credentials := &Credentials{UserID: userID}
|
||||
if err = credentials.Unmarshal(secret); err != nil {
|
||||
err = fmt.Errorf("backend/credentials: malformed secret: %v", err)
|
||||
_ = s.secrets.Delete(userID)
|
||||
log.WithError(err).Error("Could not unmarshal secret")
|
||||
return
|
||||
}
|
||||
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
// saveCredentials encrypts and saves password to the keychain store.
|
||||
func (s *Store) saveCredentials(credentials *Credentials) (err error) {
|
||||
if err = s.checkKeychain(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
credentials.Version = keychain.KeychainVersion
|
||||
|
||||
return s.secrets.Put(credentials.UserID, credentials.Marshal())
|
||||
}
|
||||
|
||||
func (s *Store) checkKeychain() (err error) {
|
||||
if s.secrets == nil {
|
||||
err = keychain.ErrNoKeychainInstalled
|
||||
log.WithError(err).Error("Store is unusable")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes credentials from the store.
|
||||
func (s *Store) Delete(userID string) (err error) {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
if err = s.checkKeychain(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return s.secrets.Delete(userID)
|
||||
}
|
||||
297
internal/users/credentials/store_test.go
Normal file
297
internal/users/credentials/store_test.go
Normal file
@ -0,0 +1,297 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const testSep = "\n"
|
||||
const secretFormat = "%v" + testSep + // UserID,
|
||||
"%v" + testSep + // Name,
|
||||
"%v" + testSep + // Emails,
|
||||
"%v" + testSep + // APIToken,
|
||||
"%v" + testSep + // Mailbox,
|
||||
"%v" + testSep + // BridgePassword,
|
||||
"%v" + testSep + // Version string
|
||||
"%v" + testSep + // Timestamp,
|
||||
"%v" + testSep + // IsHidden,
|
||||
"%v" // IsCombinedAddressMode
|
||||
|
||||
// the best would be to run this test on mac, win, and linux separately
|
||||
|
||||
type testCredentials struct {
|
||||
UserID,
|
||||
Name,
|
||||
Emails,
|
||||
APIToken,
|
||||
Mailbox,
|
||||
BridgePassword,
|
||||
Version string
|
||||
Timestamp int64
|
||||
IsHidden,
|
||||
IsCombinedAddressMode bool
|
||||
}
|
||||
|
||||
func init() { //nolint[gochecknoinits]
|
||||
gob.Register(testCredentials{})
|
||||
}
|
||||
|
||||
func (s *testCredentials) MarshalGob() string {
|
||||
buf := bytes.Buffer{}
|
||||
enc := gob.NewEncoder(&buf)
|
||||
if err := enc.Encode(s); err != nil {
|
||||
return ""
|
||||
}
|
||||
fmt.Printf("MarshalGob: %#v\n", buf.String())
|
||||
return base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||
}
|
||||
|
||||
func (s *testCredentials) Clear() {
|
||||
s.UserID = ""
|
||||
s.Name = ""
|
||||
s.Emails = ""
|
||||
s.APIToken = ""
|
||||
s.Mailbox = ""
|
||||
s.BridgePassword = ""
|
||||
s.Version = ""
|
||||
s.Timestamp = 0
|
||||
s.IsHidden = false
|
||||
s.IsCombinedAddressMode = false
|
||||
}
|
||||
|
||||
func (s *testCredentials) UnmarshalGob(secret string) error {
|
||||
s.Clear()
|
||||
b, err := base64.StdEncoding.DecodeString(secret)
|
||||
if err != nil {
|
||||
fmt.Println("decode base64", b)
|
||||
return err
|
||||
}
|
||||
buf := bytes.NewBuffer(b)
|
||||
dec := gob.NewDecoder(buf)
|
||||
if err = dec.Decode(s); err != nil {
|
||||
fmt.Println("decode gob", b, buf.Bytes())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testCredentials) ToJSON() string {
|
||||
if b, err := json.Marshal(s); err == nil {
|
||||
fmt.Printf("MarshalJSON: %#v\n", string(b))
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *testCredentials) FromJSON(secret string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = json.Unmarshal(b, s); err == nil {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *testCredentials) MarshalFmt() string {
|
||||
buf := bytes.Buffer{}
|
||||
fmt.Fprintf(
|
||||
&buf, secretFormat,
|
||||
s.UserID,
|
||||
s.Name,
|
||||
s.Emails,
|
||||
s.APIToken,
|
||||
s.Mailbox,
|
||||
s.BridgePassword,
|
||||
s.Version,
|
||||
s.Timestamp,
|
||||
s.IsHidden,
|
||||
s.IsCombinedAddressMode,
|
||||
)
|
||||
fmt.Printf("MarshalFmt: %#v\n", buf.String())
|
||||
return base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||
}
|
||||
|
||||
func (s *testCredentials) UnmarshalFmt(secret string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := bytes.NewBuffer(b)
|
||||
fmt.Println("decode fmt", b, buf.Bytes())
|
||||
_, err = fmt.Fscanf(
|
||||
buf, secretFormat,
|
||||
&s.UserID,
|
||||
&s.Name,
|
||||
&s.Emails,
|
||||
&s.APIToken,
|
||||
&s.Mailbox,
|
||||
&s.BridgePassword,
|
||||
&s.Version,
|
||||
&s.Timestamp,
|
||||
&s.IsHidden,
|
||||
&s.IsCombinedAddressMode,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testCredentials) MarshalStrings() string { // this is the most space efficient
|
||||
items := []string{
|
||||
s.UserID, // 0
|
||||
s.Name, // 1
|
||||
s.Emails, // 2
|
||||
s.APIToken, // 3
|
||||
s.Mailbox, // 4
|
||||
s.BridgePassword, // 5
|
||||
s.Version, // 6
|
||||
}
|
||||
items = append(items, fmt.Sprint(s.Timestamp)) // 7
|
||||
|
||||
if s.IsHidden { // 8
|
||||
items = append(items, "1")
|
||||
} else {
|
||||
items = append(items, "")
|
||||
}
|
||||
|
||||
if s.IsCombinedAddressMode { // 9
|
||||
items = append(items, "1")
|
||||
} else {
|
||||
items = append(items, "")
|
||||
}
|
||||
|
||||
str := strings.Join(items, sep)
|
||||
|
||||
fmt.Printf("MarshalJoin: %#v\n", str)
|
||||
return base64.StdEncoding.EncodeToString([]byte(str))
|
||||
}
|
||||
|
||||
func (s *testCredentials) UnmarshalStrings(secret string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
items := strings.Split(string(b), sep)
|
||||
if len(items) != 10 {
|
||||
return ErrWrongFormat
|
||||
}
|
||||
|
||||
s.UserID = items[0]
|
||||
s.Name = items[1]
|
||||
s.Emails = items[2]
|
||||
s.APIToken = items[3]
|
||||
s.Mailbox = items[4]
|
||||
s.BridgePassword = items[5]
|
||||
s.Version = items[6]
|
||||
if _, err = fmt.Sscanf(items[7], "%d", &s.Timestamp); err != nil {
|
||||
s.Timestamp = 0
|
||||
}
|
||||
if s.IsHidden = false; items[8] == "1" {
|
||||
s.IsHidden = true
|
||||
}
|
||||
if s.IsCombinedAddressMode = false; items[9] == "1" {
|
||||
s.IsCombinedAddressMode = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testCredentials) IsSame(rhs *testCredentials) bool {
|
||||
return s.Name == rhs.Name &&
|
||||
s.Emails == rhs.Emails &&
|
||||
s.APIToken == rhs.APIToken &&
|
||||
s.Mailbox == rhs.Mailbox &&
|
||||
s.BridgePassword == rhs.BridgePassword &&
|
||||
s.Version == rhs.Version &&
|
||||
s.Timestamp == rhs.Timestamp &&
|
||||
s.IsHidden == rhs.IsHidden &&
|
||||
s.IsCombinedAddressMode == rhs.IsCombinedAddressMode
|
||||
}
|
||||
|
||||
func TestMarshalFormats(t *testing.T) {
|
||||
input := testCredentials{UserID: "007", Emails: "ja@pm.me;jakub@cu.th", Timestamp: 152469263742, IsHidden: true}
|
||||
fmt.Printf("input %#v\n", input)
|
||||
|
||||
secretStrings := input.MarshalStrings()
|
||||
fmt.Printf("secretStrings %#v %d\n", secretStrings, len(secretStrings))
|
||||
secretGob := input.MarshalGob()
|
||||
fmt.Printf("secretGob %#v %d\n", secretGob, len(secretGob))
|
||||
secretJSON := input.ToJSON()
|
||||
fmt.Printf("secretJSON %#v %d\n", secretJSON, len(secretJSON))
|
||||
secretFmt := input.MarshalFmt()
|
||||
fmt.Printf("secretFmt %#v %d\n", secretFmt, len(secretFmt))
|
||||
|
||||
output := testCredentials{APIToken: "refresh"}
|
||||
require.NoError(t, output.UnmarshalStrings(secretStrings))
|
||||
fmt.Printf("strings out %#v \n", output)
|
||||
require.True(t, input.IsSame(&output), "strings out not same")
|
||||
|
||||
output = testCredentials{APIToken: "refresh"}
|
||||
require.NoError(t, output.UnmarshalGob(secretGob))
|
||||
fmt.Printf("gob out %#v\n \n", output)
|
||||
assert.Equal(t, input, output)
|
||||
|
||||
output = testCredentials{APIToken: "refresh"}
|
||||
require.NoError(t, output.FromJSON(secretJSON))
|
||||
fmt.Printf("json out %#v \n", output)
|
||||
require.True(t, input.IsSame(&output), "json out not same")
|
||||
|
||||
/*
|
||||
// Simple Fscanf not working!
|
||||
output = testCredentials{APIToken: "refresh"}
|
||||
require.NoError(t, output.UnmarshalFmt(secretFmt))
|
||||
fmt.Printf("fmt out %#v \n", output)
|
||||
require.True(t, input.IsSame(&output), "fmt out not same")
|
||||
*/
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
input := Credentials{
|
||||
UserID: "",
|
||||
Name: "007",
|
||||
Emails: "ja@pm.me;aj@cus.tom",
|
||||
APIToken: "sdfdsfsdfsdfsdf",
|
||||
MailboxPassword: "cdcdcdcd",
|
||||
BridgePassword: "wew123",
|
||||
Version: "k11",
|
||||
Timestamp: 152469263742,
|
||||
IsHidden: true,
|
||||
IsCombinedAddressMode: false,
|
||||
}
|
||||
fmt.Printf("input %#v\n", input)
|
||||
|
||||
secret := input.Marshal()
|
||||
fmt.Printf("secret %#v %d\n", secret, len(secret))
|
||||
|
||||
output := Credentials{APIToken: "refresh"}
|
||||
require.NoError(t, output.Unmarshal(secret))
|
||||
fmt.Printf("output %#v\n", output)
|
||||
assert.Equal(t, input, output)
|
||||
}
|
||||
Reference in New Issue
Block a user