diff options
Diffstat (limited to 'client/server.go')
| -rw-r--r-- | client/server.go | 701 |
1 files changed, 0 insertions, 701 deletions
diff --git a/client/server.go b/client/server.go deleted file mode 100644 index b3f7747..0000000 --- a/client/server.go +++ /dev/null @@ -1,701 +0,0 @@ -package client - -import ( - "time" - - "github.com/eduvpn/eduvpn-common/internal/failover" - "github.com/eduvpn/eduvpn-common/internal/http" - "github.com/eduvpn/eduvpn-common/internal/log" - "github.com/eduvpn/eduvpn-common/internal/oauth" - "github.com/eduvpn/eduvpn-common/internal/server" - discotypes "github.com/eduvpn/eduvpn-common/types/discovery" - "github.com/eduvpn/eduvpn-common/types/protocol" - srvtypes "github.com/eduvpn/eduvpn-common/types/server" - "github.com/go-errors/errors" -) - -// TODO: This should not be reliant on an internal type -func getTokens(tok oauth.Token) srvtypes.Tokens { - return srvtypes.Tokens{ - Access: tok.Access, - Refresh: tok.Refresh, - Expires: tok.ExpiredTimestamp.Unix(), - } -} - -// getConfigAuth gets a config with authorization and authentication. -// It also asks for a profile if no valid profile is found. -func (c *Client) getConfigAuth(srv server.Server, preferTCP bool, t srvtypes.Tokens) (*srvtypes.Configuration, error) { - err := c.ensureLogin(srv, t) - if err != nil { - return nil, err - } - - // TODO(jwijenbergh): Should we check if it returns false? - c.FSM.GoTransition(StateRequestConfig) - - ok, err := server.HasValidProfile(srv, c.SupportsWireguard) - if err != nil { - return nil, err - } - - // No valid profile, ask for one - if !ok { - if err = c.askProfile(srv); err != nil { - return nil, err - } - } - - // The profile has been chosen - c.FSM.GoTransition(StateChosenProfile) - - cfg, err := server.Config(srv, c.SupportsWireguard, preferTCP) - if err != nil { - return nil, err - } - - p, err := server.CurrentProfile(srv) - if err != nil { - return nil, err - } - - pCfg := &srvtypes.Configuration{ - VPNConfig: cfg.Config, - Protocol: protocol.New(cfg.Type), - DefaultGateway: p.DefaultGateway, - Tokens: getTokens(cfg.Tokens), - } - - return pCfg, nil -} - -// 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 (c *Client) retryConfigAuth(srv server.Server, preferTCP bool, t srvtypes.Tokens) (*srvtypes.Configuration, error) { - cfg, err := c.getConfigAuth(srv, preferTCP, t) - if err == nil { - return cfg, nil - } - // Only retry if the error is that the tokens are invalid - tErr := &oauth.TokensInvalidError{} - if errors.As(err, &tErr) { - // TODO: Is passing empty tokens correct here? - cfg, err = c.getConfigAuth(srv, preferTCP, srvtypes.Tokens{}) - if err == nil { - return cfg, nil - } - } - c.goBackInternal() - return nil, err -} - -// 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 (c *Client) getConfig(srv server.Server, preferTCP bool, t srvtypes.Tokens) (*srvtypes.Configuration, error) { - if c.InFSMState(StateDeregistered) { - return nil, errors.Errorf("getConfig attempt in '%v'", StateDeregistered) - } - - // Refresh the server endpoints - // This is the best effort - err := srv.RefreshEndpoints(&c.Discovery) - if err != nil { - log.Logger.Warningf("failed to refresh server endpoints: %v", err) - } - - cfg, err := c.retryConfigAuth(srv, preferTCP, t) - if err != nil { - return nil, err - } - - // Save the config - if err = c.Config.Save(&c); err != nil { - // TODO(jwijenbergh): Not sure why INFO level, yet stacktrace... - // TODO(jwijenbergh): Even worse, why logging it but then return nil? The calling code will think that everything went well. - log.Logger.Infof("c.Config.Save failed: %s\nstacktrace:\n%s", - err.Error(), err.(*errors.Error).ErrorStack()) - } - - c.FSM.GoTransition(StateGotConfig) - - return cfg, nil -} - -// Cleanup cleans up the VPN connection by sending a /disconnect to the server -func (c *Client) Cleanup(ct srvtypes.Tokens) error { - srv, err := c.Servers.GetCurrentServer() - if err != nil { - c.logError(err) - return err - } - err = srv.RefreshEndpoints(&c.Discovery) - if err != nil { - log.Logger.Warningf("failed to refresh server endpoints: %v", err) - } - - // If we need to relogin, update tokens - if server.NeedsRelogin(srv) { - server.UpdateTokens(srv, oauth.Token{ - Access: ct.Access, - Refresh: ct.Refresh, - ExpiredTimestamp: time.Unix(ct.Expires, 0), - }) - } - // update tokens to client - defer c.ForwardTokenUpdate(srv) - // Do the /disconnect API call - err = server.Disconnect(srv) - if err != nil { - // We log nothing here because this can happen regularly - // Maybe we should not log errors that we return directly anyways? - return err - } - // TODO: Tokens might be refreshed, return updated tokens - // Not implemented yet, because ideally we want this implemented with an interface - return 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 (c *Client) SetSecureLocation(countryCode string) error { - if c.InFSMState(StateAskLocation) { - defer c.locationWg.Done() - } - // Not supported with Let's Connect! - if c.isLetsConnect() { - err := errors.Errorf("discovery with Let's Connect is not supported") - c.logError(err) - return err - } - - srv, err := c.Discovery.ServerByCountryCode(countryCode) - if err != nil { - c.goBackInternal() - c.logError(err) - return err - } - - if err = c.Servers.SetSecureLocation(srv); err != nil { - c.goBackInternal() - c.logError(err) - } - - return err -} - -// 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 (c *Client) RemoveSecureInternet() error { - if c.InFSMState(StateDeregistered) { - err := errors.Errorf("RemoveSecureInternet attempt in '%v'", StateDeregistered) - c.logError(err) - return err - } - // No error because we can only have one secure internet server and if there are no secure internet servers, this is a NO-OP - c.Servers.RemoveSecureInternet() - c.FSM.GoTransition(StateNoServer) - // Save the config - if err := c.Config.Save(&c); err != nil { - // TODO(jwijenbergh): Not sure why INFO level, yet stacktrace... - // TODO(jwijenbergh): Even worse, why logging it but then return nil? The calling code will think that everything went well. - log.Logger.Infof("c.Config.Save failed: %s\nstacktrace:\n%s", - err.Error(), err.(*errors.Error).ErrorStack()) - } - 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 (c *Client) RemoveInstituteAccess(url string) error { - if c.InFSMState(StateDeregistered) { - err := errors.Errorf("RemoveInstituteAccess attempt in '%v'", StateDeregistered) - c.logError(err) - return err - } - // No error because this is a NO-OP if the server doesn't exist - c.Servers.RemoveInstituteAccess(url) - c.FSM.GoTransition(StateNoServer) - // Save the config - if err := c.Config.Save(&c); err != nil { - // TODO(jwijenbergh): Not sure why INFO level, yet stacktrace... - // TODO(jwijenbergh): Even worse, why logging it but then return nil? The calling code will think that everything went well. - log.Logger.Infof("c.Config.Save failed: %s\nstacktrace:\n%s", - err.Error(), err.(*errors.Error).ErrorStack()) - } - 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 (c *Client) RemoveCustomServer(url string) error { - if c.InFSMState(StateDeregistered) { - err := errors.Errorf("RemoveCustomServer attempt in '%v'", StateDeregistered) - c.logError(err) - return err - } - // No error because this is a NO-OP if the server doesn't exist - c.Servers.RemoveCustomServer(url) - c.FSM.GoTransition(StateNoServer) - // Save the config - if err := c.Config.Save(&c); err != nil { - // TODO(jwijenbergh): Not sure why INFO level, yet stacktrace... - // TODO(jwijenbergh): Even worse, why logging it but then return nil? The calling code will think that everything went well. - log.Logger.Infof("c.Config.Save failed: %s\nstacktrace:\n%s", - err.Error(), err.(*errors.Error).ErrorStack()) - } - return nil -} - -// AddInstituteServer adds an Institute Access server by `url`. -func (c *Client) AddInstituteServer(url string) (err error) { - defer func() { - if err != nil { - c.logError(err) - } - }() - - // Not supported with Let's Connect! - if c.isLetsConnect() { - return errors.Errorf("adding and Institute Access server with Let's Connect is not supported") - } - - // Indicate that we're loading the server - c.FSM.GoTransition(StateLoadingServer) - - // Check if we are able to fetch discovery, and log if something went wrong - if _, err := c.DiscoServers(); err != nil { - log.Logger.Warningf("Failed to get discovery servers: %v", err) - } - - if _, err := c.DiscoOrganizations(); err != nil { - log.Logger.Warningf("Failed to get discovery organizations: %v", err) - } - - // FIXME: Do nothing with discovery here as the client already has it - // So pass a server as the parameter - var dSrv *discotypes.Server - dSrv, err = c.Discovery.ServerByURL(url, "institute_access") - if err != nil { - c.goBackInternal() - return err - } - - // Add the secure internet server - srv, err := c.Servers.AddInstituteAccessServer(dSrv) - if err != nil { - c.goBackInternal() - return err - } - - // Set the server as the current so OAuth can be cancelled - if err = c.Servers.SetInstituteAccess(srv); err != nil { - c.goBackInternal() - return err - } - - // Indicate that we want to authorize this server - c.FSM.GoTransition(StateChosenServer) - - // Authorize it - if err = c.ensureLogin(srv, srvtypes.Tokens{}); err != nil { - // Removing is best effort - _ = c.RemoveInstituteAccess(url) - return err - } - - c.FSM.GoTransition(StateNoServer) - return 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 (c *Client) AddSecureInternetHomeServer(orgID string) (err error) { - defer func() { - if err != nil { - c.logError(err) - } - }() - - // Not supported with Let's Connect! - if c.isLetsConnect() { - return errors.Errorf("adding a secure internet server with Let's Connect is not supported") - } - - // Indicate that we're loading the server - c.FSM.GoTransition(StateLoadingServer) - - // Check if we are able to fetch discovery, and log if something went wrong - if _, err := c.DiscoServers(); err != nil { - log.Logger.Warningf("Failed to get discovery servers: %v", err) - } - - if _, err := c.DiscoOrganizations(); err != nil { - log.Logger.Warningf("Failed to get discovery organizations: %v", err) - } - - // Get the secure internet URL from discovery - org, dSrv, err := c.Discovery.SecureHomeArgs(orgID) - if err != nil { - // We mark the organizations as expired because we got an error - // Note that in the docs it states that it only should happen when the Org ID doesn't exist - // However, this is nice as well because it also catches the error where the SecureInternetHome server is not found - c.Discovery.MarkOrganizationsExpired() - c.goBackInternal() - return err - } - - // Add the secure internet server - srv, err := c.Servers.AddSecureInternet(org, dSrv) - if err != nil { - c.goBackInternal() - return err - } - - // TODO(jwijenbergh): Does this call transfers execution flow to UI? - if err = c.askSecureLocation(); err != nil { - // Removing is the best effort - // This already goes back to the main screen - _ = c.RemoveSecureInternet() - return err - } - - c.FSM.GoTransition(StateChosenLocation) - - // Set the server as the current so OAuth can be cancelled - if err = c.Servers.SetSecureInternet(srv); err != nil { - c.goBackInternal() - return err - } - - // Server has been chosen for authentication - c.FSM.GoTransition(StateChosenServer) - - // Authorize it - if err = c.ensureLogin(srv, srvtypes.Tokens{}); err != nil { - // Removing is best effort - _ = c.RemoveSecureInternet() - return err - } - c.FSM.GoTransition(StateNoServer) - return nil -} - -// AddCustomServer adds a Custom Server by `url`. -func (c *Client) AddCustomServer(url string) (err error) { - defer func() { - if err != nil { - c.logError(err) - } - }() - - if url, err = http.EnsureValidURL(url); err != nil { - return err - } - - // Indicate that we're loading the server - c.FSM.GoTransition(StateLoadingServer) - - customServer := &discotypes.Server{ - BaseURL: url, - DisplayName: map[string]string{"en": url}, - Type: "custom_server", - } - - // A custom server is just an institute access server under the hood - srv, err := c.Servers.AddCustomServer(customServer) - if err != nil { - c.goBackInternal() - return err - } - - // Set the server as the current so OAuth can be cancelled - if err = c.Servers.SetCustomServer(srv); err != nil { - c.goBackInternal() - return err - } - - // Server has been chosen for authentication - c.FSM.GoTransition(StateChosenServer) - - // Authorize it - if err = c.ensureLogin(srv, srvtypes.Tokens{}); err != nil { - // removing is best effort - _ = c.RemoveCustomServer(url) - return err - } - - c.FSM.GoTransition(StateNoServer) - return 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 (c *Client) GetConfigInstituteAccess(url string, preferTCP bool, t srvtypes.Tokens) (cfg *srvtypes.Configuration, err error) { - defer func() { - if err != nil { - c.logError(err) - } - }() - - // Not supported with Let's Connect! - if c.isLetsConnect() { - return nil, errors.Errorf("discovery with Let's Connect is not supported") - } - - c.FSM.GoTransition(StateLoadingServer) - - // Get the server if it exists - var srv *server.InstituteAccessServer - if srv, err = c.Servers.GetInstituteAccess(url); err != nil { - c.goBackInternal() - return nil, err - } - - // Set the server as the current - if err = c.Servers.SetInstituteAccess(srv); err != nil { - return nil, err - } - - // The server has now been chosen - c.FSM.GoTransition(StateChosenServer) - - if cfg, err = c.getConfig(srv, preferTCP, t); err != nil { - c.goBackInternal() - } - - // Also forward tokens using the callback - c.ForwardTokenUpdate(srv) - - return cfg, err -} - -// 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. -// TODO: Check on first argument orgID -func (c *Client) GetConfigSecureInternet(_ string, preferTCP bool, t srvtypes.Tokens) (cfg *srvtypes.Configuration, err error) { - defer func() { - if err != nil { - c.logError(err) - } - }() - - log.Logger.Debugf("getting config for secure internet server with org ID: '%s", orgID) - - // Not supported with Let's Connect! - if c.isLetsConnect() { - return nil, errors.Errorf("discovery with Let's Connect is not supported") - } - - c.FSM.GoTransition(StateLoadingServer) - - // Get the server if it exists - var srv *server.SecureInternetHomeServer - if srv, err = c.Servers.GetSecureInternetHomeServer(); err != nil { - c.goBackInternal() - return nil, err - } - - // Set the server as the current - if err = c.Servers.SetSecureInternet(srv); err != nil { - return nil, err - } - - c.FSM.GoTransition(StateChosenServer) - - if cfg, err = c.getConfig(srv, preferTCP, t); err != nil { - c.goBackInternal() - } - - // Also forward tokens using the callback - c.ForwardTokenUpdate(srv) - - return cfg, err -} - -// 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 (c *Client) GetConfigCustomServer(url string, preferTCP bool, t srvtypes.Tokens) (cfg *srvtypes.Configuration, err error) { - defer func() { - if err != nil { - c.logError(err) - } - }() - - if url, err = http.EnsureValidURL(url); err != nil { - return nil, err - } - - c.FSM.GoTransition(StateLoadingServer) - - // Get the server if it exists - var srv *server.InstituteAccessServer - if srv, err = c.Servers.GetCustomServer(url); err != nil { - c.goBackInternal() - return nil, err - } - - // Set the server as the current - if err = c.Servers.SetCustomServer(srv); err != nil { - c.goBackInternal() - return nil, err - } - - c.FSM.GoTransition(StateChosenServer) - - if cfg, err = c.getConfig(srv, preferTCP, t); err != nil { - c.goBackInternal() - } - - // Also forward tokens using the callback - c.ForwardTokenUpdate(srv) - - return cfg, err -} - -// askSecureLocation asks the user to choose a Secure Internet location by moving the FSM to the STATE_ASK_LOCATION state. -func (c *Client) askSecureLocation() error { - loc := c.Discovery.SecureLocationList() - - c.locationWg.Add(1) - // Ask for the location in the callback - if err := c.FSM.GoTransitionRequired(StateAskLocation, loc); err != nil { - return err - } - - c.locationWg.Wait() - - // The state has changed, meaning setting the secure location was not successful - if c.FSM.Current != StateAskLocation { - log.Logger.Debugf("fsm failed to transit; expected %v / actual %v", GetStateName(StateAskLocation), GetStateName(c.FSM.Current)) - return errors.New("failed loading secure internet location") - } - return nil -} - -// RenewSession renews the session for the current VPN server. -// This logs the user back in. -func (c *Client) RenewSession() (err error) { - defer func() { - if err != nil { - c.logError(err) - } - }() - - var srv server.Server - if srv, err = c.Servers.GetCurrentServer(); err != nil { - return err - } - - err = srv.RefreshEndpoints(&c.Discovery) - if err != nil { - log.Logger.Warningf("failed to refresh server endpoints: %v", err) - } - - // The server has not been chosen yet, this means that we want to manually renew - if !c.FSM.InState(StateChosenServer) { - c.FSM.GoTransition(StateLoadingServer) - c.FSM.GoTransition(StateChosenServer) - } - - server.MarkTokensForRenew(srv) - return c.ensureLogin(srv, srvtypes.Tokens{}) -} - -// ensureLogin logs the user back in if needed. -// It runs the FSM transitions to ask for user input. -func (c *Client) ensureLogin(srv server.Server, t srvtypes.Tokens) (err error) { - // Relogin with oauth - // This moves the state to authorized - if !server.NeedsRelogin(srv) { - // OAuth was valid, ensure we are in the authorized state - c.FSM.GoTransition(StateAuthorized) - return nil - } - - // Try again but update the tokens using the client provided tokens - server.UpdateTokens(srv, oauth.Token{ - Access: t.Access, - Refresh: t.Refresh, - ExpiredTimestamp: time.Unix(t.Expires, 0), - }) - if !server.NeedsRelogin(srv) { - // OAuth was valid, ensure we are in the authorized state - c.FSM.GoTransition(StateAuthorized) - return nil - } - - // Mark organizations as expired if the server is a secure internet server - b, err := srv.Base() - // We only try to update it when we found the server base - if err == nil && b.Type == "secure_internet" { - c.Discovery.MarkOrganizationsExpired() - } - - // Tokens are not valid or the client gave an error when updating tokens - // Otherwise, do the OAuth exchange - var url string - if url, err = server.OAuthURL(srv, c.Name); err != nil { - return err - } - - if err = c.FSM.GoTransitionRequired(StateOAuthStarted, url); err != nil { - return err - } - - if err = server.OAuthExchange(srv); err != nil { - c.goBackInternal() - } - c.FSM.GoTransition(StateAuthorized) - - return err -} - -// 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 (c *Client) SetProfileID(profileID string) (err error) { - if c.InFSMState(StateAskProfile) { - defer c.profileWg.Done() - } - defer func() { - if err != nil { - c.logError(err) - } - }() - - var srv server.Server - if srv, err = c.Servers.GetCurrentServer(); err != nil { - c.goBackInternal() - return err - } - - var b *server.Base - if b, err = srv.Base(); err != nil { - c.goBackInternal() - return err - } - b.Profiles.Current = profileID - - return nil -} - -func (c *Client) StartFailover(gateway string, wgMTU int, readRxBytes func() (int64, error)) (bool, error) { - if c.Failover != nil { - return false, errors.New("another failover process is already started") - } - c.Failover = failover.New(readRxBytes) - - return c.Failover.Start(gateway, wgMTU) -} - -func (c *Client) CancelFailover() error { - if c.Failover == nil { - return errors.New("no failover process") - } - c.Failover.Cancel() - return nil -} |
