summaryrefslogtreecommitdiff
path: root/internal/discovery/discovery.go
diff options
context:
space:
mode:
authorJeroen Wijenbergh <jeroen.wijenbergh@geant.org>2025-08-29 14:36:00 +0200
committerJeroen Wijenbergh <jeroen.wijenbergh@geant.org>2025-08-29 14:40:25 +0200
commit132782f44603dfdc3b1d875d632f786109ee09a2 (patch)
tree71cd1cefee78636167c560c80c8bc79c8982fe26 /internal/discovery/discovery.go
parent4a23134e1e5d70a9c8c5857790dbf27585ca3b1f (diff)
Discovery: Remove manager and DiscoveryStartup
Diffstat (limited to 'internal/discovery/discovery.go')
-rw-r--r--internal/discovery/discovery.go59
1 files changed, 36 insertions, 23 deletions
diff --git a/internal/discovery/discovery.go b/internal/discovery/discovery.go
index 80a69eb..512756b 100644
--- a/internal/discovery/discovery.go
+++ b/internal/discovery/discovery.go
@@ -8,6 +8,7 @@ import (
"fmt"
"log/slog"
"net/http"
+ "sync"
"time"
httpw "codeberg.org/eduVPN/eduvpn-common/internal/http"
@@ -83,6 +84,8 @@ func (s *Server) Score(search string) int {
// Discovery is the main structure used for this package.
type Discovery struct {
+ // mu is the read write mutex that protects the struct from concurrent access
+ mu sync.RWMutex
// The httpClient for sending HTTP requests
httpClient *httpw.Client
@@ -174,23 +177,27 @@ func (discovery *Discovery) file(ctx context.Context, jsonFile string, previousV
// MarkOrganizationsExpired marks the organizations as expired
func (discovery *Discovery) MarkOrganizationsExpired() {
+ discovery.mu.Lock()
+ defer discovery.mu.Unlock()
// Re-initialize the timestamp to zero
discovery.OrganizationList.Timestamp = time.Time{}
}
// MarkServersExpired marks the servers as expired
func (discovery *Discovery) MarkServersExpired() {
+ discovery.mu.Lock()
+ defer discovery.mu.Unlock()
// Re-initialize the timestamp to zero
discovery.ServerList.Timestamp = time.Time{}
}
-// DetermineOrganizationsUpdate returns a boolean indicating whether or not the discovery organizations should be updated
+// determineOrganizationsUpdate returns a boolean indicating whether or not the discovery organizations should be updated
// https://github.com/eduvpn/documentation/blob/v3/SERVER_DISCOVERY.md
// - [IMPLEMENTED] on "first launch" when offering the search for "Institute Access" and "Organizations";
// - [IMPLEMENTED in client/client.go and here] when the user tries to add new server AND the user did NOT yet choose an organization before; Implemented in Register()
// - [IMPLEMENTED in client/client.go] when the authorization for the server associated with an already chosen organization is triggered, e.g. after expiry or revocation.
// - [IMPLEMENTED here] NOTE: when the org_id that the user chose previously is no longer available in organization_list.json the application should ask the user to choose their organization (again). This can occur for example when the organization replaced their identity provider, uses a different domain after rebranding or simply ceased to exist.
-func (discovery *Discovery) DetermineOrganizationsUpdate() bool {
+func (discovery *Discovery) determineOrganizationsUpdate() bool {
if discovery.OrganizationList.Timestamp.IsZero() {
return true
}
@@ -202,6 +209,8 @@ func (discovery *Discovery) DetermineOrganizationsUpdate() bool {
// SecureLocationList returns a slice of all the available locations.
func (discovery *Discovery) SecureLocationList() []string {
+ discovery.mu.RLock()
+ defer discovery.mu.RUnlock()
var loc []string
for _, srv := range discovery.ServerList.List {
if srv.Type == "secure_internet" {
@@ -217,6 +226,8 @@ func (discovery *Discovery) ServerByURL(
baseURL string,
srvType string,
) (*Server, error) {
+ discovery.mu.RLock()
+ defer discovery.mu.RUnlock()
for _, currentServer := range discovery.ServerList.List {
if currentServer.BaseURL == baseURL && currentServer.Type == srvType {
return &currentServer, nil
@@ -237,6 +248,8 @@ func (cnf *ErrCountryNotFound) Error() string {
// ServerByCountryCode returns the discovery server by the country code
// An error is returned if and only if nil is returned for the server.
func (discovery *Discovery) ServerByCountryCode(countryCode string) (*Server, error) {
+ discovery.mu.RLock()
+ defer discovery.mu.RUnlock()
for _, srv := range discovery.ServerList.List {
if srv.CountryCode == countryCode && srv.Type == "secure_internet" {
return &srv, nil
@@ -261,8 +274,10 @@ func (discovery *Discovery) orgByID(orgID string) (*Organization, error) {
// - The secure internet server itself
// An error is returned if and only if nil is returned for the organization.
func (discovery *Discovery) SecureHomeArgs(orgID string) (*Organization, *Server, error) {
+ discovery.mu.RLock()
org, err := discovery.orgByID(orgID)
if err != nil {
+ discovery.mu.RUnlock()
discovery.MarkOrganizationsExpired()
return nil, nil, err
}
@@ -270,17 +285,19 @@ func (discovery *Discovery) SecureHomeArgs(orgID string) (*Organization, *Server
// Get a server with the base url
srv, err := discovery.ServerByURL(org.SecureInternetHome, "secure_internet")
if err != nil {
+ discovery.mu.RUnlock()
discovery.MarkOrganizationsExpired()
return nil, nil, err
}
+ discovery.mu.RUnlock()
return org, srv, nil
}
-// DetermineServersUpdate returns whether or not the discovery servers should be updated by contacting the discovery server
+// determineServersUpdate returns whether or not the discovery servers should be updated by contacting the discovery server
// https://github.com/eduvpn/documentation/blob/v3/SERVER_DISCOVERY.md
// - [Implemented] The application MUST always fetch the server_list.json at application start.
// - The application MAY refresh the server_list.json periodically, e.g. once every hour.
-func (discovery *Discovery) DetermineServersUpdate() bool {
+func (discovery *Discovery) determineServersUpdate() bool {
// No servers, we should update
if discovery.ServerList.Timestamp.IsZero() {
return true
@@ -305,9 +322,14 @@ func (discovery *Discovery) cachedOrgs() *Organizations {
// If there was an error, a cached copy is returned if available.
// cache is set to true if there should be no network call done
func (discovery *Discovery) Organizations(ctx context.Context, cache bool) (*Organizations, bool, error) {
- if cache || !discovery.DetermineOrganizationsUpdate() {
+ discovery.mu.RLock()
+ if cache || !discovery.determineOrganizationsUpdate() {
+ discovery.mu.RUnlock()
return discovery.cachedOrgs(), false, nil
}
+ discovery.mu.RUnlock()
+ discovery.mu.Lock()
+ defer discovery.mu.Unlock()
file := "organization_list.json"
var jsonDecode Organizations
update, err := discovery.file(ctx, file, discovery.OrganizationList.Version, discovery.OrganizationList.UpdateHeader, &jsonDecode)
@@ -349,9 +371,14 @@ func (discovery *Discovery) cachedServers() *Servers {
// If there was an error, a cached copy is returned if available.
// cache is set to true if there should be no network call done
func (discovery *Discovery) Servers(ctx context.Context, cache bool) (*Servers, bool, error) {
- if cache || !discovery.DetermineServersUpdate() {
+ discovery.mu.RLock()
+ if cache || !discovery.determineServersUpdate() {
+ discovery.mu.RUnlock()
return discovery.cachedServers(), false, nil
}
+ discovery.mu.RUnlock()
+ discovery.mu.Lock()
+ defer discovery.mu.Unlock()
file := "server_list.json"
var jsonDecode Servers
update, err := discovery.file(ctx, file, discovery.ServerList.Version, discovery.ServerList.UpdateHeader, &jsonDecode)
@@ -397,29 +424,15 @@ func (discovery *Discovery) UpdateOrganizations(other Organizations) {
}
}
-// Copy creates a deep-copy for the discovery struct
-// It does this by marshalling and unmarshalling it as JSON
-func (discovery *Discovery) Copy() (Discovery, error) {
- var dest Discovery
- b, err := json.Marshal(discovery)
- if err != nil {
- return dest, err
- }
-
- err = json.Unmarshal(b, &dest)
- if err != nil {
- return dest, err
- }
-
- return dest, nil
-}
-
// Fill makes sure that the cache is filled with the embedded discovery
func (discovery *Discovery) Fill() error {
if !HasCache {
return nil
}
+ discovery.mu.Lock()
+ defer discovery.mu.Unlock()
+
var err error
var es Servers
err = errors.Join(err, json.Unmarshal(eServers, &es))