feat: clientmanager has checkconnection

This commit is contained in:
James Houlahan
2020-04-16 16:05:05 +02:00
parent bfc4069df4
commit 4809d97cb1
11 changed files with 93 additions and 102 deletions

2
go.sum
View File

@ -183,7 +183,9 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/therecipe/qt v0.0.0-20200126204426-5074eb6d8c41 h1:yBVcrpbaQYJBdKT2pxTdlL4hBE/eM4UPcyj9YpyvSok=
github.com/therecipe/qt v0.0.0-20200126204426-5074eb6d8c41/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200126204426-5074eb6d8c41 h1:My9HYsfDI/fJPZGyilw6066buBiZ7pgKRRgAyvKK5lA=
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200126204426-5074eb6d8c41/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200126204426-5074eb6d8c41 h1:jTzKrQ6EIPvKw1B9/wwoKJLrXF+ManMsXoUzufxAdsg=
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200126204426-5074eb6d8c41/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk=
github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=

View File

@ -535,6 +535,12 @@ func (b *Bridge) DisallowProxy() {
b.clientManager.DisallowProxy()
}
// CheckConnection returns whether there is an internet connection.
// This should use the connection manager when it is eventually implemented.
func (b *Bridge) CheckConnection() error {
return b.clientManager.CheckConnection()
}
func (b *Bridge) updateCurrentUserAgent() {
UpdateCurrentUserAgent(b.version, b.userAgentOS, b.userAgentClientName, b.userAgentClientVersion)
}

View File

@ -238,6 +238,20 @@ func (mr *MockClientManagerMockRecorder) AllowProxy() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllowProxy", reflect.TypeOf((*MockClientManager)(nil).AllowProxy))
}
// CheckConnection mocks base method
func (m *MockClientManager) CheckConnection() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CheckConnection")
ret0, _ := ret[0].(error)
return ret0
}
// CheckConnection indicates an expected call of CheckConnection
func (mr *MockClientManagerMockRecorder) CheckConnection() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckConnection", reflect.TypeOf((*MockClientManager)(nil).CheckConnection))
}
// DisallowProxy mocks base method
func (m *MockClientManager) DisallowProxy() {
m.ctrl.T.Helper()

View File

@ -58,4 +58,5 @@ type ClientManager interface {
AllowProxy()
DisallowProxy()
GetAuthUpdateChannel() chan pmapi.ClientAuth
CheckConnection() error
}

View File

@ -23,7 +23,6 @@ import (
"strings"
"github.com/ProtonMail/proton-bridge/internal/preferences"
"github.com/ProtonMail/proton-bridge/pkg/connection"
"github.com/ProtonMail/proton-bridge/pkg/ports"
"github.com/abiosoft/ishell"
)
@ -41,7 +40,7 @@ func (f *frontendCLI) restart(c *ishell.Context) {
}
func (f *frontendCLI) checkInternetConnection(c *ishell.Context) {
if connection.CheckInternetConnection() == nil {
if f.bridge.CheckConnection() == nil {
f.Println("Internet connection is available.")
} else {
f.Println("Can not contact server please check you internet connection.")

View File

@ -49,7 +49,7 @@ import (
//"github.com/ProtonMail/proton-bridge/pkg/keychain"
"github.com/ProtonMail/proton-bridge/pkg/listener"
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/pkg/updates"
"github.com/kardianos/osext"
"github.com/skratchdot/open-golang/open"
@ -86,7 +86,7 @@ type FrontendQt struct {
programName string // Program name (shown in taskbar).
programVer string // Program version (shown in help).
authClient bridge.PMAPIProvider
authClient pmapi.Client
auth *pmapi.Auth
@ -569,7 +569,7 @@ func (s *FrontendQt) isSMTPSTARTTLS() bool {
}
func (s *FrontendQt) checkInternet() {
s.Qml.SetConnectionStatus(IsInternetAvailable())
s.Qml.SetConnectionStatus(s.bridge.CheckConnection() == nil)
}
func (s *FrontendQt) switchAddressModeUser(iAccount int) {

View File

@ -25,7 +25,6 @@ import (
"os/exec"
"time"
"github.com/ProtonMail/proton-bridge/pkg/connection"
"github.com/therecipe/qt/core"
)
@ -64,10 +63,6 @@ func PauseLong() {
time.Sleep(3 * time.Second)
}
func IsInternetAvailable() bool {
return connection.CheckInternetConnection() == nil
}
// FIXME: Not working in test...
func WaitForEnter() {
log.Print("Press 'Enter' to continue...")

View File

@ -54,6 +54,7 @@ type Bridger interface {
ClearData() error
AllowProxy()
DisallowProxy()
CheckConnection() error
}
// BridgeUser is an interface of user needed by frontend.

View File

@ -1,90 +0,0 @@
// 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 connection
import (
"errors"
"fmt"
"net/http"
"github.com/ProtonMail/proton-bridge/pkg/logs"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
// Errors for possible connection issues
var (
ErrNoInternetConnection = errors.New("no internet connection")
ErrCanNotReachAPI = errors.New("can not reach PM API")
log = logs.GetLogEntry("connection") //nolint[gochecknoglobals]
)
// CheckInternetConnection does a check of API connection. It checks two of our endpoints in parallel.
// One endpoint is part of the protonmail API, while the other is not.
// This allows us to determine whether there is a problem with the connection itself or only a problem with our API.
// Two errors can be returned, ErrNoInternetConnection or ErrCanNotReachAPI.
func CheckInternetConnection() error {
client := &http.Client{
// TODO: Set transport properly! (Need access to ClientManager somehow)
// Transport: pmapi.NewDialerWithPinning(pmapi.CurrentUserAgent).TransportWithPinning(),
}
// Do not cumulate timeouts, use goroutines.
retStatus := make(chan error)
retAPI := make(chan error)
// Check protonstatus.com without SSL for performance reasons. vpn_status endpoint is fast and
// returns only OK; this endpoint is not known by the public. We check the connection only.
go checkConnection(client, "http://protonstatus.com/vpn_status", retStatus)
// Check of API reachability also uses a fast endpoint.
// TODO: This should check active proxy, not the RootURL
go checkConnection(client, pmapi.RootURL+"/tests/ping", retAPI)
errStatus := <-retStatus
errAPI := <-retAPI
if errStatus != nil {
if errAPI != nil {
log.Error("Checking internet connection failed with ", errStatus, " and ", errAPI)
return ErrNoInternetConnection
}
log.Warning("API OK, but status: ", errStatus)
return nil
}
if errAPI != nil {
log.Error("Status OK, but API: ", errAPI)
return ErrCanNotReachAPI
}
return nil
}
func checkConnection(client *http.Client, url string, errorChannel chan error) {
resp, err := client.Get(url)
if err != nil {
errorChannel <- err
return
}
_ = resp.Body.Close()
if resp.StatusCode != 200 {
errorChannel <- fmt.Errorf("HTTP status code %d", resp.StatusCode)
return
}
errorChannel <- nil
}

View File

@ -15,7 +15,7 @@
// 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 connection
package pmapi
import (
"net/http"

View File

@ -255,6 +255,68 @@ func (cm *ClientManager) GetClientAuthChannel() chan ClientAuth {
return cm.clientAuths
}
// Errors for possible connection issues
var (
ErrNoInternetConnection = errors.New("no internet connection")
ErrCanNotReachAPI = errors.New("can not reach PM API")
)
// CheckConnection returns an error if there is no internet connection.
// This should be moved to the ConnectionManager when it is implemented.
func (cm *ClientManager) CheckConnection() error {
client := getHTTPClient(cm.config, cm.roundTripper)
// Do not cumulate timeouts, use goroutines.
retStatus := make(chan error)
retAPI := make(chan error)
// Check protonstatus.com without SSL for performance reasons. vpn_status endpoint is fast and
// returns only OK; this endpoint is not known by the public. We check the connection only.
go checkConnection(client, "http://protonstatus.com/vpn_status", retStatus)
// Check of API reachability also uses a fast endpoint.
go checkConnection(client, cm.GetRootURL()+"/tests/ping", retAPI)
errStatus := <-retStatus
errAPI := <-retAPI
switch {
case errStatus == nil && errAPI == nil:
return nil
case errStatus == nil && errAPI != nil:
cm.log.Error("ProtonStatus is reachable but API is not")
return ErrCanNotReachAPI
case errStatus != nil && errAPI == nil:
cm.log.Warn("API is reachable but protonstatus is not")
return nil
case errStatus != nil && errAPI != nil:
cm.log.Error("Both ProtonStatus and API are unreachable")
return ErrNoInternetConnection
}
return nil
}
func checkConnection(client *http.Client, url string, errorChannel chan error) {
resp, err := client.Get(url)
if err != nil {
errorChannel <- err
return
}
_ = resp.Body.Close()
if resp.StatusCode != 200 {
errorChannel <- fmt.Errorf("HTTP status code %d", resp.StatusCode)
return
}
errorChannel <- nil
}
// forwardClientAuths handles all incoming auths from clients before forwarding them on the bridge auth channel.
func (cm *ClientManager) forwardClientAuths() {
for auth := range cm.clientAuths {
@ -266,7 +328,7 @@ func (cm *ClientManager) forwardClientAuths() {
}
// SetTokenIfUnset sets the token for the given userID if it wasn't already set.
// The token does not expire.
// The set token does not expire.
func (cm *ClientManager) SetTokenIfUnset(userID, token string) {
cm.tokensLocker.Lock()
defer cm.tokensLocker.Unlock()
@ -352,6 +414,7 @@ func (cm *ClientManager) handleClientAuth(ca ClientAuth) {
cm.setToken(ca.UserID, ca.Auth.GenToken(), time.Duration(ca.Auth.ExpiresIn)*time.Second)
}
// watchTokenExpirations refreshes any tokens which are about to expire.
func (cm *ClientManager) watchTokenExpirations() {
for userID := range cm.expiredTokens {
log := cm.log.WithField("userID", userID)