summaryrefslogtreecommitdiff
path: root/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'client.go')
-rw-r--r--client.go1144
1 files changed, 0 insertions, 1144 deletions
diff --git a/client.go b/client.go
deleted file mode 100644
index 39ff109..0000000
--- a/client.go
+++ /dev/null
@@ -1,1144 +0,0 @@
-package eduvpn
-
-import (
- "errors"
- "fmt"
- "strings"
-
- "github.com/eduvpn/eduvpn-common/internal/config"
- "github.com/eduvpn/eduvpn-common/internal/discovery"
- "github.com/eduvpn/eduvpn-common/internal/fsm"
- "github.com/eduvpn/eduvpn-common/internal/log"
- "github.com/eduvpn/eduvpn-common/internal/oauth"
- "github.com/eduvpn/eduvpn-common/internal/server"
- "github.com/eduvpn/eduvpn-common/internal/util"
- "github.com/eduvpn/eduvpn-common/types"
-)
-
-type (
- // ServerBase is an alias to the internal ServerBase
- // This contains the details for each server
- ServerBase = server.ServerBase
-)
-
-func (client Client) isLetsConnect() bool {
- // see https://git.sr.ht/~fkooman/vpn-user-portal/tree/v3/item/src/OAuth/ClientDb.php
- return strings.HasPrefix(client.Name, "org.letsconnect-vpn.app")
-}
-
-// Client is the main struct for the VPN client
-type Client struct {
- // The name of the client
- Name string `json:"-"`
-
- // The language used for language matching
- Language string `json:"-"` // language should not be saved
-
- // The chosen server
- Servers server.Servers `json:"servers"`
-
- // The list of servers and organizations from disco
- Discovery discovery.Discovery `json:"discovery"`
-
- // The fsm
- FSM fsm.FSM `json:"-"`
-
- // The logger
- Logger log.FileLogger `json:"-"`
-
- // The config
- Config config.Config `json:"-"`
-
- // Whether to enable debugging
- Debug bool `json:"-"`
-}
-
-// Register initializes the clientwith the following parameters:
-// - name: the name of the client
-// - directory: the directory where the config files are stored. Absolute or relative
-// - stateCallback: the callback function for the FSM that takes two states (old and new) and the data as an interface
-// - debug: whether or not we want to enable debugging
-// It returns an error if initialization failed, for example when discovery cannot be obtained and when there are no servers.
-func (client *Client) Register(
- name string,
- directory string,
- language string,
- stateCallback func(FSMStateID, FSMStateID, interface{}),
- debug bool,
-) error {
- errorMessage := "failed to register with the GO library"
- if !client.InFSMState(STATE_DEREGISTERED) {
- return &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: FSMDeregisteredError{}.CustomError(),
- }
- }
- client.Name = name
-
- // TODO: Verify language setting?
- client.Language = language
-
- // Initialize the logger
- logLevel := log.LOG_WARNING
- if debug {
- logLevel = log.LOG_INFO
- }
-
- loggerErr := client.Logger.Init(logLevel, name, directory)
- if loggerErr != nil {
- return &types.WrappedErrorMessage{Message: errorMessage, Err: loggerErr}
- }
-
- // Initialize the FSM
- client.FSM = newFSM(stateCallback, directory, debug)
- client.Debug = debug
-
- // Initialize the Config
- client.Config.Init(directory, "state")
-
- // Try to load the previous configuration
- if client.Config.Load(&client) != nil {
- // This error can be safely ignored, as when the config does not load, the struct will not be filled
- client.Logger.Info("Previous configuration not found")
- }
-
- // Go to the No Server state with the saved servers after we're done
- defer client.FSM.GoTransitionWithData(STATE_NO_SERVER, client.Servers, true)
-
- // Let's Connect! doesn't care about discovery
- if client.isLetsConnect() {
- return nil
- }
-
- // Check if we are able to fetch discovery, and log if something went wrong
- _, discoServersErr := client.GetDiscoServers()
- if discoServersErr != nil {
- client.Logger.Warning(fmt.Sprintf("Failed to get discovery servers: %v", discoServersErr))
- }
- _, discoOrgsErr := client.GetDiscoOrganizations()
- if discoOrgsErr != nil {
- client.Logger.Warning(fmt.Sprintf("Failed to get discovery organizations: %v", discoOrgsErr))
- }
-
- return nil
-}
-
-// Deregister 'deregisters' the client, meaning saving the log file and the config and emptying out the client struct.
-func (client *Client) Deregister() {
- // Close the log file
- client.Logger.Close()
-
- // Save the config
- saveErr := client.Config.Save(&client)
- if saveErr != nil {
- client.Logger.Info(
- fmt.Sprintf(
- "Failed saving configuration, error: %s",
- types.GetErrorTraceback(saveErr),
- ),
- )
- }
-
- // Empty out the state
- *client = Client{}
-}
-
-// goBackInternal uses the public go back but logs an error if it happened.
-func (client *Client) goBackInternal() {
- goBackErr := client.GoBack()
- if goBackErr != nil {
- client.Logger.Info(
- fmt.Sprintf(
- "Failed going back, error: %s",
- types.GetErrorTraceback(goBackErr),
- ),
- )
- }
-}
-
-// GoBack transitions the FSM back to the previous UI state, for now this is always the NO_SERVER state.
-func (client *Client) GoBack() error {
- errorMessage := "failed to go back"
- if client.InFSMState(STATE_DEREGISTERED) {
- client.Logger.Error("Wrong state, cannot go back when deregistered")
- return &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: FSMDeregisteredError{}.CustomError(),
- }
- }
-
- // FIXME: Abitrary back transitions don't work because we need the approriate data
- client.FSM.GoTransitionWithData(STATE_NO_SERVER, client.Servers, false)
- return nil
-}
-
-// ensureLogin logs the user back in if needed.
-// It runs the FSM transitions to ask for user input.
-func (client *Client) ensureLogin(chosenServer server.Server) error {
- errorMessage := "failed ensuring login"
- // Relogin with oauth
- // This moves the state to authorized
- if server.NeedsRelogin(chosenServer) {
- url, urlErr := server.GetOAuthURL(chosenServer, client.Name)
-
- client.FSM.GoTransitionWithData(STATE_OAUTH_STARTED, url, true)
-
- if urlErr != nil {
- client.goBackInternal()
- return &types.WrappedErrorMessage{Message: errorMessage, Err: urlErr}
- }
-
- exchangeErr := server.OAuthExchange(chosenServer)
-
- if exchangeErr != nil {
- client.goBackInternal()
- return &types.WrappedErrorMessage{Message: errorMessage, Err: exchangeErr}
- }
- }
- // OAuth was valid, ensure we are in the authorized state
- client.FSM.GoTransition(STATE_AUTHORIZED)
- return nil
-}
-
-// getConfigAuth gets a config with authorization and authentication.
-// It also asks for a profile if no valid profile is found.
-func (client *Client) getConfigAuth(
- chosenServer server.Server,
- preferTCP bool,
-) (string, string, error) {
- loginErr := client.ensureLogin(chosenServer)
- if loginErr != nil {
- return "", "", loginErr
- }
- client.FSM.GoTransition(STATE_REQUEST_CONFIG)
-
- validProfile, profileErr := server.HasValidProfile(chosenServer)
- if profileErr != nil {
- return "", "", profileErr
- }
-
- // No valid profile, ask for one
- if !validProfile {
- askProfileErr := client.askProfile(chosenServer)
- if askProfileErr != nil {
- return "", "", askProfileErr
- }
- }
-
- // We return the error otherwise we wrap it too much
- return server.GetConfig(chosenServer, preferTCP)
-}
-
-// retryConfigAuth retries the getConfigAuth function if the tokens are invalid.
-// If OAuth is cancelled, it makes sure that we only forward the error as additional info.
-func (client *Client) retryConfigAuth(
- chosenServer server.Server,
- preferTCP bool,
-) (string, string, error) {
- errorMessage := "failed authorized config retry"
- config, configType, configErr := client.getConfigAuth(chosenServer, preferTCP)
- if configErr != nil {
- level := types.ERR_OTHER
- var error *oauth.OAuthTokensInvalidError
- var oauthCancelledError *oauth.OAuthCancelledCallbackError
-
- // Only retry if the error is that the tokens are invalid
- if errors.As(configErr, &error) {
- config, configType, configErr = client.getConfigAuth(
- chosenServer,
- preferTCP,
- )
- if configErr == nil {
- return config, configType, nil
- }
- }
- if errors.As(configErr, &oauthCancelledError) {
- level = types.ERR_INFO
- }
- client.goBackInternal()
- return "", "", &types.WrappedErrorMessage{Level: level, Message: errorMessage, Err: configErr}
- }
- return config, configType, nil
-}
-
-// getConfig gets an OpenVPN/WireGuard configuration by contacting the server, moving the FSM towards the DISCONNECTED state and then saving the local configuration file.
-func (client *Client) getConfig(
- chosenServer server.Server,
- preferTCP bool,
-) (string, string, error) {
- errorMessage := "failed to get a configuration for OpenVPN/Wireguard"
- if client.InFSMState(STATE_DEREGISTERED) {
- return "", "", &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: FSMDeregisteredError{}.CustomError(),
- }
- }
-
- config, configType, configErr := client.retryConfigAuth(chosenServer, preferTCP)
-
- if configErr != nil {
- return "", "", &types.WrappedErrorMessage{Level: types.GetErrorLevel(configErr), Message: errorMessage, Err: configErr}
- }
-
- currentServer, currentServerErr := client.Servers.GetCurrentServer()
- if currentServerErr != nil {
- return "", "", &types.WrappedErrorMessage{Message: errorMessage, Err: currentServerErr}
- }
-
- // Signal the server display info
- client.FSM.GoTransitionWithData(STATE_DISCONNECTED, currentServer, false)
-
- // Save the config
- saveErr := client.Config.Save(&client)
- if saveErr != nil {
- client.Logger.Info(
- fmt.Sprintf(
- "Failed saving configuration after getting a server: %s",
- types.GetErrorTraceback(saveErr),
- ),
- )
- }
-
- return config, configType, nil
-}
-
-// SetSecureLocation sets the location for the current secure location server. countryCode is the secure location to be chosen.
-// This function returns an error e.g. if the server cannot be found or the location is wrong.
-func (client *Client) SetSecureLocation(countryCode string) error {
- errorMessage := "failed asking secure location"
-
- // Not supported with Let's Connect!
- if client.isLetsConnect() {
- return &types.WrappedErrorMessage{Message: errorMessage, Err: LetsConnectNotSupportedError{}}
- }
-
- server, serverErr := client.Discovery.GetServerByCountryCode(countryCode, "secure_internet")
- if serverErr != nil {
- client.Logger.Error(
- fmt.Sprintf(
- "Failed getting secure internet server by country code: %s with error: %s",
- countryCode,
- types.GetErrorTraceback(serverErr),
- ),
- )
- client.goBackInternal()
- return &types.WrappedErrorMessage{Message: errorMessage, Err: serverErr}
- }
-
- setLocationErr := client.Servers.SetSecureLocation(server)
- if setLocationErr != nil {
- client.Logger.Error(
- fmt.Sprintf(
- "Failed setting secure internet server with error: %s",
- types.GetErrorTraceback(setLocationErr),
- ),
- )
- client.goBackInternal()
- return &types.WrappedErrorMessage{Message: errorMessage, Err: setLocationErr}
- }
- return nil
-}
-
-// askProfile asks the user for a profile by moving the FSM to the ASK_PROFILE state.
-func (client *Client) askProfile(chosenServer server.Server) error {
- base, baseErr := chosenServer.GetBase()
- if baseErr != nil {
- return &types.WrappedErrorMessage{Message: "failed asking for profiles", Err: baseErr}
- }
- client.FSM.GoTransitionWithData(STATE_ASK_PROFILE, &base.Profiles, false)
- return nil
-}
-
-// askSecureLocation asks the user to choose a Secure Internet location by moving the FSM to the STATE_ASK_LOCATION state.
-func (client *Client) askSecureLocation() error {
- locations := client.Discovery.GetSecureLocationList()
-
- // Ask for the location in the callback
- client.FSM.GoTransitionWithData(STATE_ASK_LOCATION, locations, false)
-
- // The state has changed, meaning setting the secure location was not successful
- if client.FSM.Current != STATE_ASK_LOCATION {
- // TODO: maybe a custom type for this errors.new?
- return &types.WrappedErrorMessage{
- Message: "failed setting secure location",
- Err: errors.New("failed loading secure location"),
- }
- }
- return nil
-}
-
-// RemoveSecureInternet removes the current secure internet server.
-// It returns an error if the server cannot be removed due to the state being DEREGISTERED.
-// Note that if the server does not exist, it returns nil as an error.
-func (client *Client) RemoveSecureInternet() error {
- if client.InFSMState(STATE_DEREGISTERED) {
- client.Logger.Error("Failed removing secure internet server due to deregistered")
- return &types.WrappedErrorMessage{
- Message: "failed to remove Secure Internet",
- Err: FSMDeregisteredError{}.CustomError(),
- }
- }
- // No error because we can only have one secure internet server and if there are no secure internet servers, this is a NO-OP
- client.Servers.RemoveSecureInternet()
- client.FSM.GoTransitionWithData(STATE_NO_SERVER, client.Servers, false)
- // Save the config
- saveErr := client.Config.Save(&client)
- if saveErr != nil {
- client.Logger.Info(
- fmt.Sprintf(
- "Failed saving configuration after removing a secure internet server: %s",
- types.GetErrorTraceback(saveErr),
- ),
- )
- }
- return nil
-}
-
-// RemoveInstituteAccess removes the institute access server with `url`.
-// It returns an error if the server cannot be removed due to the state being DEREGISTERED.
-// Note that if the server does not exist, it returns nil as an error.
-func (client *Client) RemoveInstituteAccess(url string) error {
- if client.InFSMState(STATE_DEREGISTERED) {
- return &types.WrappedErrorMessage{
- Message: "failed to remove Institute Access",
- Err: FSMDeregisteredError{}.CustomError(),
- }
- }
- // No error because this is a NO-OP if the server doesn't exist
- client.Servers.RemoveInstituteAccess(url)
- client.FSM.GoTransitionWithData(STATE_NO_SERVER, client.Servers, false)
- // Save the config
- saveErr := client.Config.Save(&client)
- if saveErr != nil {
- client.Logger.Info(
- fmt.Sprintf(
- "Failed saving configuration after removing an institute access server: %s",
- types.GetErrorTraceback(saveErr),
- ),
- )
- }
- return nil
-}
-
-// RemoveCustomServer removes the custom server with `url`.
-// It returns an error if the server cannot be removed due to the state being DEREGISTERED.
-// Note that if the server does not exist, it returns nil as an error.
-func (client *Client) RemoveCustomServer(url string) error {
- if client.InFSMState(STATE_DEREGISTERED) {
- return &types.WrappedErrorMessage{
- Message: "failed to remove Custom Server",
- Err: FSMDeregisteredError{}.CustomError(),
- }
- }
- // No error because this is a NO-OP if the server doesn't exist
- client.Servers.RemoveCustomServer(url)
- client.FSM.GoTransitionWithData(STATE_NO_SERVER, client.Servers, false)
- // Save the config
- saveErr := client.Config.Save(&client)
- if saveErr != nil {
- client.Logger.Info(
- fmt.Sprintf(
- "Failed saving configuration after removing a custom server: %s",
- types.GetErrorTraceback(saveErr),
- ),
- )
- }
- return nil
-}
-
-// AddInstituteServer adds an Institute Access server by `url`.
-func (client *Client) AddInstituteServer(url string) (server.Server, error) {
- errorMessage := fmt.Sprintf("failed adding Institute Access server with url %s", url)
-
- // Not supported with Let's Connect!
- if client.isLetsConnect() {
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: LetsConnectNotSupportedError{}}
- }
-
- // Indicate that we're loading the server
- client.FSM.GoTransition(STATE_LOADING_SERVER)
-
- // FIXME: Do nothing with discovery here as the client already has it
- // So pass a server as the parameter
- instituteServer, discoErr := client.Discovery.GetServerByURL(url, "institute_access")
- if discoErr != nil {
- client.goBackInternal()
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: discoErr}
- }
-
- // Add the secure internet server
- server, serverErr := client.Servers.AddInstituteAccessServer(instituteServer)
- if serverErr != nil {
- client.goBackInternal()
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: serverErr}
- }
-
- // Indicate that we want to authorize this server
- client.FSM.GoTransition(STATE_CHOSEN_SERVER)
-
- // Authorize it
- loginErr := client.ensureLogin(server)
- if loginErr != nil {
- // Removing is best effort
- _ = client.RemoveInstituteAccess(url)
- return nil, &types.WrappedErrorMessage{Level: types.GetErrorLevel(loginErr), Message: errorMessage, Err: loginErr}
- }
-
- client.FSM.GoTransitionWithData(STATE_NO_SERVER, client.Servers, false)
- return server, nil
-}
-
-// AddSecureInternetHomeServer adds a Secure Internet Home Server with `orgID` that was obtained from the Discovery file.
-// Because there is only one Secure Internet Home Server, it replaces the existing one.
-func (client *Client) AddSecureInternetHomeServer(orgID string) (server.Server, error) {
- errorMessage := fmt.Sprintf(
- "failed adding Secure Internet home server with organization ID %s",
- orgID,
- )
-
- // Not supported with Let's Connect!
- if client.isLetsConnect() {
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: LetsConnectNotSupportedError{}}
- }
-
- // Indicate that we're loading the server
- client.FSM.GoTransition(STATE_LOADING_SERVER)
-
- // Get the secure internet URL from discovery
- secureOrg, secureServer, discoErr := client.Discovery.GetSecureHomeArgs(orgID)
- if discoErr != nil {
- client.goBackInternal()
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: discoErr}
- }
-
- // Add the secure internet server
- server, serverErr := client.Servers.AddSecureInternet(secureOrg, secureServer)
- if serverErr != nil {
- client.goBackInternal()
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: serverErr}
- }
-
- locationErr := client.askSecureLocation()
- if locationErr != nil {
- // Removing is best effort
- _ = client.RemoveSecureInternet()
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: locationErr}
- }
-
- // Server has been chosen for authentication
- client.FSM.GoTransition(STATE_CHOSEN_SERVER)
-
- // Authorize it
- loginErr := client.ensureLogin(server)
- if loginErr != nil {
- // Removing is best effort
- _ = client.RemoveSecureInternet()
- return nil, &types.WrappedErrorMessage{Level: types.GetErrorLevel(loginErr), Message: errorMessage, Err: loginErr}
- }
- client.FSM.GoTransitionWithData(STATE_NO_SERVER, client.Servers, false)
- return server, nil
-}
-
-// AddCustomServer adds a Custom Server by `url`
-func (client *Client) AddCustomServer(url string) (server.Server, error) {
- errorMessage := fmt.Sprintf("failed adding Custom server with url %s", url)
-
- url, urlErr := util.EnsureValidURL(url)
- if urlErr != nil {
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: urlErr}
- }
-
- // Indicate that we're loading the server
- client.FSM.GoTransition(STATE_LOADING_SERVER)
-
- customServer := &types.DiscoveryServer{
- BaseURL: url,
- DisplayName: map[string]string{"en": url},
- Type: "custom_server",
- }
-
- // A custom server is just an institute access server under the hood
- server, serverErr := client.Servers.AddCustomServer(customServer)
- if serverErr != nil {
- client.goBackInternal()
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: serverErr}
- }
-
- // Server has been chosen for authentication
- client.FSM.GoTransition(STATE_CHOSEN_SERVER)
-
- // Authorize it
- loginErr := client.ensureLogin(server)
- if loginErr != nil {
- // removing is best effort
- _ = client.RemoveCustomServer(url)
- return nil, &types.WrappedErrorMessage{Level: types.GetErrorLevel(loginErr), Message: errorMessage, Err: loginErr}
- }
-
- client.FSM.GoTransitionWithData(STATE_NO_SERVER, client.Servers, false)
- return server, nil
-}
-
-// GetConfigInstituteAccess gets a configuration for an Institute Access Server.
-// It ensures that the Institute Access Server exists by creating or using an existing one with the url.
-// `preferTCP` indicates that the client wants to use TCP (through OpenVPN) to establish the VPN tunnel.
-func (client *Client) GetConfigInstituteAccess(url string, preferTCP bool) (string, string, error) {
- errorMessage := fmt.Sprintf("failed getting a configuration for Institute Access %s", url)
-
- // Not supported with Let's Connect!
- if client.isLetsConnect() {
- return "", "", &types.WrappedErrorMessage{Message: errorMessage, Err: LetsConnectNotSupportedError{}}
- }
-
- client.FSM.GoTransition(STATE_LOADING_SERVER)
-
- // Get the server if it exists
- server, serverErr := client.Servers.GetInstituteAccess(url)
- if serverErr != nil {
- client.Logger.Error(
- fmt.Sprintf(
- "Failed getting an institute access server configuration with error: %s",
- types.GetErrorTraceback(serverErr),
- ),
- )
- client.goBackInternal()
- return "", "", &types.WrappedErrorMessage{Message: errorMessage, Err: serverErr}
- }
-
- // Set the server as the current
- currentErr := client.Servers.SetInstituteAccess(server)
- if currentErr != nil {
- return "", "", &types.WrappedErrorMessage{Message: errorMessage, Err: currentErr}
- }
-
- // The server has now been chosen
- client.FSM.GoTransition(STATE_CHOSEN_SERVER)
-
- config, configType, configErr := client.getConfig(server, preferTCP)
- if configErr != nil {
- client.Logger.Inherit(configErr,
- fmt.Sprintf(
- "Failed getting an institute access server configuration with error: %s",
- types.GetErrorTraceback(configErr),
- ),
- )
- client.goBackInternal()
- return "", "", &types.WrappedErrorMessage{Level: types.GetErrorLevel(configErr), Message: errorMessage, Err: configErr}
- }
- return config, configType, nil
-}
-
-// GetConfigSecureInternet gets a configuration for a Secure Internet Server.
-// It ensures that the Secure Internet Server exists by creating or using an existing one with the orgID.
-// `preferTCP` indicates that the client wants to use TCP (through OpenVPN) to establish the VPN tunnel.
-func (client *Client) GetConfigSecureInternet(
- orgID string,
- preferTCP bool,
-) (string, string, error) {
- errorMessage := fmt.Sprintf(
- "failed getting a configuration for Secure Internet organization %s",
- orgID,
- )
-
- // Not supported with Let's Connect!
- if client.isLetsConnect() {
- return "", "", &types.WrappedErrorMessage{Message: errorMessage, Err: LetsConnectNotSupportedError{}}
- }
-
- client.FSM.GoTransition(STATE_LOADING_SERVER)
-
- // Get the server if it exists
- server, serverErr := client.Servers.GetSecureInternetHomeServer()
- if serverErr != nil {
- client.Logger.Error(
- fmt.Sprintf(
- "Failed getting a custom server configuration with error: %s",
- types.GetErrorTraceback(serverErr),
- ),
- )
- client.goBackInternal()
- return "", "", &types.WrappedErrorMessage{Message: errorMessage, Err: serverErr}
- }
-
- // Set the server as the current
- currentErr := client.Servers.SetSecureInternet(server)
- if currentErr != nil {
- return "", "", &types.WrappedErrorMessage{Message: errorMessage, Err: currentErr}
- }
-
- client.FSM.GoTransition(STATE_CHOSEN_SERVER)
-
- config, configType, configErr := client.getConfig(server, preferTCP)
- if configErr != nil {
- client.Logger.Inherit(
- configErr,
- fmt.Sprintf(
- "Failed getting a secure internet configuration with error: %s",
- types.GetErrorTraceback(configErr),
- ),
- )
- client.goBackInternal()
- return "", "", &types.WrappedErrorMessage{Level: types.GetErrorLevel(configErr), Message: errorMessage, Err: configErr}
- }
- return config, configType, nil
-}
-
-// GetConfigCustomServer gets a configuration for a Custom Server.
-// It ensures that the Custom Server exists by creating or using an existing one with the url.
-// `preferTCP` indicates that the client wants to use TCP (through OpenVPN) to establish the VPN tunnel.
-func (client *Client) GetConfigCustomServer(url string, preferTCP bool) (string, string, error) {
- errorMessage := fmt.Sprintf("failed getting a configuration for custom server %s", url)
-
- url, urlErr := util.EnsureValidURL(url)
- if urlErr != nil {
- return "", "", &types.WrappedErrorMessage{Message: errorMessage, Err: urlErr}
- }
-
- client.FSM.GoTransition(STATE_LOADING_SERVER)
-
- // Get the server if it exists
- server, serverErr := client.Servers.GetCustomServer(url)
- if serverErr != nil {
- client.Logger.Error(
- fmt.Sprintf(
- "Failed getting a custom server configuration with error: %s",
- types.GetErrorTraceback(serverErr),
- ),
- )
- client.goBackInternal()
- return "", "", &types.WrappedErrorMessage{Message: errorMessage, Err: serverErr}
- }
-
- // Set the server as the current
- currentErr := client.Servers.SetCustomServer(server)
- if currentErr != nil {
- return "", "", &types.WrappedErrorMessage{Message: errorMessage, Err: currentErr}
- }
-
- client.FSM.GoTransition(STATE_CHOSEN_SERVER)
-
- config, configType, configErr := client.getConfig(server, preferTCP)
- if configErr != nil {
- client.Logger.Inherit(
- configErr,
- fmt.Sprintf(
- "Failed getting a custom server with error: %s",
- types.GetErrorTraceback(configErr),
- ),
- )
- client.goBackInternal()
- return "", "", &types.WrappedErrorMessage{Level: types.GetErrorLevel(configErr), Message: errorMessage, Err: configErr}
- }
- return config, configType, nil
-}
-
-// CancelOAuth cancels OAuth if one is in progress.
-// If OAuth is not in progress, it returns an error.
-// An error is also returned if OAuth is in progress but it fails to cancel it.
-func (client *Client) CancelOAuth() error {
- errorMessage := "failed to cancel OAuth"
- if !client.InFSMState(STATE_OAUTH_STARTED) {
- client.Logger.Error("Failed cancelling OAuth, not in the right state")
- return &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: FSMWrongStateError{
- Got: client.FSM.Current,
- Want: STATE_OAUTH_STARTED,
- }.CustomError(),
- }
- }
-
- currentServer, serverErr := client.Servers.GetCurrentServer()
- if serverErr != nil {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed cancelling OAuth, no server configured to cancel OAuth for (err: %v)",
- serverErr,
- ),
- )
- return &types.WrappedErrorMessage{Message: errorMessage, Err: serverErr}
- }
- server.CancelOAuth(currentServer)
- return nil
-}
-
-// ChangeSecureLocation changes the location for an existing Secure Internet Server.
-// Changing a secure internet location is only possible when the user is in the main screen (STATE_NO_SERVER), otherwise it returns an error.
-// It also returns an error if something has gone wrong when selecting the new location
-func (client *Client) ChangeSecureLocation() error {
- errorMessage := "failed to change location from the main screen"
-
- if !client.InFSMState(STATE_NO_SERVER) {
- client.Logger.Error("Failed changing secure internet location, not in the right state")
- return &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: FSMWrongStateError{
- Got: client.FSM.Current,
- Want: STATE_NO_SERVER,
- }.CustomError(),
- }
- }
-
- askLocationErr := client.askSecureLocation()
- if askLocationErr != nil {
- client.Logger.Error(
- fmt.Sprintf(
- "Failed changing secure internet location, err: %s",
- types.GetErrorTraceback(askLocationErr),
- ),
- )
- return &types.WrappedErrorMessage{Message: errorMessage, Err: askLocationErr}
- }
-
- // Go back to the main screen
- client.FSM.GoTransitionWithData(STATE_NO_SERVER, client.Servers, false)
-
- return nil
-}
-
-// GetDiscoOrganizations gets the organizations list from the discovery server
-// If the list cannot be retrieved an error is returned.
-// If this is the case then a previous version of the list is returned if there is any.
-// This takes into account the frequency of updates, see: https://github.com/eduvpn/documentation/blob/v3/SERVER_DISCOVERY.md#organization-list.
-func (client *Client) GetDiscoOrganizations() (*types.DiscoveryOrganizations, error) {
- errorMessage := "failed getting discovery organizations list"
- // Not supported with Let's Connect!
- if client.isLetsConnect() {
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: LetsConnectNotSupportedError{}}
- }
-
- orgs, orgsErr := client.Discovery.GetOrganizationsList()
- if orgsErr != nil {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed getting discovery organizations, Err: %s",
- types.GetErrorTraceback(orgsErr),
- ),
- )
- return nil, &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: orgsErr,
- }
- }
- return orgs, nil
-}
-
-// GetDiscoServers gets the servers list from the discovery server
-// If the list cannot be retrieved an error is returned.
-// If this is the case then a previous version of the list is returned if there is any.
-// This takes into account the frequency of updates, see: https://github.com/eduvpn/documentation/blob/v3/SERVER_DISCOVERY.md#server-list.
-func (client *Client) GetDiscoServers() (*types.DiscoveryServers, error) {
- errorMessage := "failed getting discovery servers list"
-
- // Not supported with Let's Connect!
- if client.isLetsConnect() {
- return nil, &types.WrappedErrorMessage{Message: errorMessage, Err: LetsConnectNotSupportedError{}}
- }
-
- servers, serversErr := client.Discovery.GetServersList()
- if serversErr != nil {
- client.Logger.Warning(
- fmt.Sprintf("Failed getting discovery servers, Err: %s", types.GetErrorTraceback(serversErr)),
- )
- return nil, &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: serversErr,
- }
- }
- return servers, nil
-}
-
-// SetProfileID sets a `profileID` for the current server.
-// An error is returned if this is not possible, for example when no server is configured.
-func (client *Client) SetProfileID(profileID string) error {
- errorMessage := "failed to set the profile ID for the current server"
- server, serverErr := client.Servers.GetCurrentServer()
- if serverErr != nil {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed setting a profile ID because no server configured, Err: %s",
- types.GetErrorTraceback(serverErr),
- ),
- )
- client.goBackInternal()
- return &types.WrappedErrorMessage{Message: errorMessage, Err: serverErr}
- }
-
- base, baseErr := server.GetBase()
- if baseErr != nil {
- client.Logger.Error(
- fmt.Sprintf("Failed setting a profile ID, Err: %s", types.GetErrorTraceback(serverErr)),
- )
- client.goBackInternal()
- return &types.WrappedErrorMessage{Message: errorMessage, Err: baseErr}
- }
- base.Profiles.Current = profileID
- return nil
-}
-
-// SetSearchServer sets the FSM to the SEARCH_SERVER state.
-// This indicates that the user wants to search for a new server.
-// Returns an error if this state transition is not possible.
-func (client *Client) SetSearchServer() error {
- if !client.FSM.HasTransition(STATE_SEARCH_SERVER) {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed setting search server, wrong state %s",
- GetStateName(client.FSM.Current),
- ),
- )
- return &types.WrappedErrorMessage{
- Message: "failed to set search server",
- Err: FSMWrongStateTransitionError{
- Got: client.FSM.Current,
- Want: STATE_SEARCH_SERVER,
- }.CustomError(),
- }
- }
-
- client.FSM.GoTransition(STATE_SEARCH_SERVER)
- return nil
-}
-
-// SetConnected sets the FSM to the CONNECTED state.
-// This indicates that the VPN is connected to the server.
-// Returns an error if this state transition is not possible.
-func (client *Client) SetConnected() error {
- errorMessage := "failed to set connected"
- if client.InFSMState(STATE_CONNECTED) {
- // already connected, show no error
- client.Logger.Warning("Already connected")
- return nil
- }
- if !client.FSM.HasTransition(STATE_CONNECTED) {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed setting connected, wrong state: %s",
- GetStateName(client.FSM.Current),
- ),
- )
- return &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: FSMWrongStateTransitionError{
- Got: client.FSM.Current,
- Want: STATE_CONNECTED,
- }.CustomError(),
- }
- }
-
- currentServer, currentServerErr := client.Servers.GetCurrentServer()
- if currentServerErr != nil {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed setting connected, cannot get current server with error: %s",
- types.GetErrorTraceback(currentServerErr),
- ),
- )
- return &types.WrappedErrorMessage{Message: errorMessage, Err: currentServerErr}
- }
-
- client.FSM.GoTransitionWithData(STATE_CONNECTED, currentServer, false)
- return nil
-}
-
-// SetConnecting sets the FSM to the CONNECTING state.
-// This indicates that the VPN is currently connecting to the server.
-// Returns an error if this state transition is not possible.
-func (client *Client) SetConnecting() error {
- errorMessage := "failed to set connecting"
- if client.InFSMState(STATE_CONNECTING) {
- // already loading connection, show no error
- client.Logger.Warning("Already connecting")
- return nil
- }
- if !client.FSM.HasTransition(STATE_CONNECTING) {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed setting connecting, wrong state: %s",
- GetStateName(client.FSM.Current),
- ),
- )
- return &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: FSMWrongStateTransitionError{
- Got: client.FSM.Current,
- Want: STATE_CONNECTING,
- }.CustomError(),
- }
- }
-
- currentServer, currentServerErr := client.Servers.GetCurrentServer()
- if currentServerErr != nil {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed setting connecting, cannot get current server with error: %s",
- types.GetErrorTraceback(currentServerErr),
- ),
- )
- return &types.WrappedErrorMessage{Message: errorMessage, Err: currentServerErr}
- }
-
- client.FSM.GoTransitionWithData(STATE_CONNECTING, currentServer, false)
- return nil
-}
-
-// SetDisconnecting sets the FSM to the DISCONNECTING state.
-// This indicates that the VPN is currently disconnecting from the server.
-// Returns an error if this state transition is not possible.
-func (client *Client) SetDisconnecting() error {
- errorMessage := "failed to set disconnecting"
- if client.InFSMState(STATE_DISCONNECTING) {
- // already disconnecting, show no error
- client.Logger.Warning("Already disconnecting")
- return nil
- }
- if !client.FSM.HasTransition(STATE_DISCONNECTING) {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed setting disconnecting, wrong state: %s",
- GetStateName(client.FSM.Current),
- ),
- )
- return &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: FSMWrongStateTransitionError{
- Got: client.FSM.Current,
- Want: STATE_DISCONNECTING,
- }.CustomError(),
- }
- }
-
- currentServer, currentServerErr := client.Servers.GetCurrentServer()
- if currentServerErr != nil {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed setting disconnected, cannot get current server with error: %s",
- types.GetErrorTraceback(currentServerErr),
- ),
- )
- return &types.WrappedErrorMessage{Message: errorMessage, Err: currentServerErr}
- }
-
- client.FSM.GoTransitionWithData(STATE_DISCONNECTING, currentServer, false)
- return nil
-}
-
-// SetDisconnected sets the FSM to the DISCONNECTED state.
-// This indicates that the VPN is currently disconnected from the server.
-// This also sends the /disconnect API call to the server.
-// Returns an error if this state transition is not possible.
-func (client *Client) SetDisconnected(cleanup bool) error {
- errorMessage := "failed to set disconnected"
- if client.InFSMState(STATE_DISCONNECTED) {
- // already disconnected, show no error
- client.Logger.Warning("Already disconnected")
- return nil
- }
- if !client.FSM.HasTransition(STATE_DISCONNECTED) {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed setting disconnected, wrong state: %s",
- GetStateName(client.FSM.Current),
- ),
- )
- return &types.WrappedErrorMessage{
- Message: errorMessage,
- Err: FSMWrongStateTransitionError{
- Got: client.FSM.Current,
- Want: STATE_DISCONNECTED,
- }.CustomError(),
- }
- }
-
- currentServer, currentServerErr := client.Servers.GetCurrentServer()
- if currentServerErr != nil {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed setting disconnect, failed getting current server with error: %s",
- types.GetErrorTraceback(currentServerErr),
- ),
- )
- return &types.WrappedErrorMessage{Message: errorMessage, Err: currentServerErr}
- }
-
- if cleanup {
- // Do the /disconnect API call and go to disconnected after...
- server.Disconnect(currentServer)
- }
-
- client.FSM.GoTransitionWithData(STATE_DISCONNECTED, currentServer, false)
-
- return nil
-}
-
-// RenewSession renews the session for the current VPN server.
-// This logs the user back in.
-func (client *Client) RenewSession() error {
- errorMessage := "failed to renew session"
-
- currentServer, currentServerErr := client.Servers.GetCurrentServer()
- if currentServerErr != nil {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed getting current server to renew, error: %s",
- types.GetErrorTraceback(currentServerErr),
- ),
- )
- return &types.WrappedErrorMessage{Message: errorMessage, Err: currentServerErr}
- }
-
- server.MarkTokensForRenew(currentServer)
- loginErr := client.ensureLogin(currentServer)
- if loginErr != nil {
- client.Logger.Warning(
- fmt.Sprintf(
- "Failed logging in server for renew, error: %s",
- types.GetErrorTraceback(loginErr),
- ),
- )
- return &types.WrappedErrorMessage{Message: errorMessage, Err: loginErr}
- }
-
- return nil
-}
-
-// ShouldRenewButton returns true if the renew button should be shown
-// If there is no server then this returns false and logs with INFO if so
-// In other cases it simply checks the expiry time and calculates according to: https://github.com/eduvpn/documentation/blob/b93854dcdd22050d5f23e401619e0165cb8bc591/API.md#session-expiry.
-func (client *Client) ShouldRenewButton() bool {
- if !client.InFSMState(STATE_CONNECTED) && !client.InFSMState(STATE_CONNECTING) &&
- !client.InFSMState(STATE_DISCONNECTED) &&
- !client.InFSMState(STATE_DISCONNECTING) {
- return false
- }
-
- currentServer, currentServerErr := client.Servers.GetCurrentServer()
-
- if currentServerErr != nil {
- client.Logger.Info(
- fmt.Sprintf(
- "No server found to renew with err: %s",
- types.GetErrorTraceback(currentServerErr),
- ),
- )
- return false
- }
-
- return server.ShouldRenewButton(currentServer)
-}
-
-// InFSMState is a helper to check if the FSM is in state `checkState`.
-func (client *Client) InFSMState(checkState FSMStateID) bool {
- return client.FSM.InState(checkState)
-}
-
-// GetTranslated gets the translation for `languages` using the current state language.
-func (client *Client) GetTranslated(languages map[string]string) string {
- return util.GetLanguageMatched(languages, client.Language)
-}
-
-type LetsConnectNotSupportedError struct{}
-
-func (e LetsConnectNotSupportedError) Error() string {
- return "Any operation that involves discovery is not allowed with the Let's Connect! client"
-}