summaryrefslogtreecommitdiff
path: root/internal/server/secureinternet.go
blob: e0d081a8dc58d7780c8ab2107fe82a2cc667fc55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package server

import (
	"context"
	"errors"
	"log/slog"
	"net/url"
	"strings"
	"time"

	"codeberg.org/eduVPN/eduvpn-common/internal/api"
	"codeberg.org/eduVPN/eduvpn-common/internal/config/v2"
	"codeberg.org/eduVPN/eduvpn-common/internal/discovery"
	"codeberg.org/eduVPN/eduvpn-common/types/server"
	"github.com/jwijenbergh/eduoauth-go"
)

// ReplaceWAYF replaces an authorization template containing of @RETURN_TO@ and @ORG_ID@ with the authorization URL and the organization ID
// See https://github.com/eduvpn/documentation/blob/dc4d53c47dd7a69e95d6650eec408e16eaa814a2/SERVER_DISCOVERY_SKIP_WAYF.md
func ReplaceWAYF(template string, authURL string, orgID string) string {
	// We just return the authURL in the cases where the template is not given or is invalid
	if template == "" {
		return authURL
	}
	if !strings.Contains(template, "@RETURN_TO@") {
		return authURL
	}
	if !strings.Contains(template, "@ORG_ID@") {
		return authURL
	}
	// Replace authURL
	template = strings.Replace(template, "@RETURN_TO@", url.QueryEscape(authURL), 1)

	// If now there is no more ORG_ID, return as there weren't enough @ symbols
	if !strings.Contains(template, "@ORG_ID@") {
		return authURL
	}
	// Replace ORG ID
	template = strings.Replace(template, "@ORG_ID@", url.QueryEscape(orgID), 1)
	return template
}

// AddSecure adds a secure internet server
// `ctx` is the context used for cancellation
// `disco` are the discovery servers
// `orgID` is the organiztaion ID
// `ot` specifies specifies the start time OAuth was already triggered
func (s *Servers) AddSecure(ctx context.Context, discom *discovery.Manager, orgID string, ot *int64) error {
	if s.config.HasSecureInternet() {
		return errors.New("a secure internet server already exists")
	}
	disco, release := discom.Discovery(false)
	dorg, dsrv, err := disco.SecureHomeArgs(orgID)
	if err != nil {
		release()
		return err
	}
	release()

	sd := api.ServerData{
		ID:         dorg.OrgID,
		Type:       server.TypeSecureInternet,
		BaseWK:     dsrv.BaseURL,
		BaseAuthWK: dsrv.BaseURL,
		ProcessAuth: func(ctx context.Context, url string) (string, error) {
			newd, release := discom.Discovery(true)
			defer release()
			// the only thing we can do is log warn
			// this is already done in the functions
			newd.Servers(ctx)       //nolint:errcheck
			newd.Organizations(ctx) //nolint:errcheck
			updorg, updsrv, err := newd.SecureHomeArgs(orgID)
			if err != nil {
				return "", err
			}
			ret := ReplaceWAYF(updsrv.AuthenticationURLTemplate, url, updorg.OrgID)
			return ret, nil
		},
	}

	auth := time.Time{}
	if ot != nil {
		auth = time.Unix(*ot, 0)
	}

	err = s.config.AddServer(orgID, server.TypeSecureInternet, v2.Server{
		CountryCode:       dsrv.CountryCode,
		LastAuthorizeTime: auth,
	})
	if err != nil {
		return err
	}

	// no authorization should be triggered, return
	if ot != nil {
		return nil
	}

	// Authorize by creating the API object
	_, err = api.NewAPI(ctx, s.clientID, sd, s.cb, nil)
	if err != nil {
		// authorization has failed, remove the server again
		rerr := s.config.RemoveServer(orgID, server.TypeSecureInternet)
		if rerr != nil {
			slog.Warn("could not remove secure internet server after failing authorization", "server", orgID, "error", rerr)
		}
		return err
	}
	return nil
}

// GetSecure gets a secure internet server
// `ctx` is the context used for cancellation
// `orgID` is the organization ID that identifies the server
// `disco` are the discovery servers
// `tok` are the tokens such that the server can be found without triggering auth
// `disableAuth` is set to true when authorization should not be triggered
func (s *Servers) GetSecure(ctx context.Context, orgID string, discom *discovery.Manager, tok *eduoauth.Token, disableAuth bool) (*Server, error) {
	srv, err := s.config.GetServer(orgID, server.TypeSecureInternet)
	if err != nil {
		return nil, err
	}

	disco, release := discom.Discovery(false)
	dorg, dhome, err := disco.SecureHomeArgs(orgID)
	if err != nil {
		release()
		return nil, err
	}

	dloc, err := disco.ServerByCountryCode(srv.CountryCode)
	if err != nil {
		release()
		return nil, err
	}
	release()

	sd := api.ServerData{
		ID:         dorg.OrgID,
		Type:       server.TypeSecureInternet,
		BaseWK:     dloc.BaseURL,
		BaseAuthWK: dhome.BaseURL,
		ProcessAuth: func(ctx context.Context, url string) (string, error) {
			newd, release := discom.Discovery(true)
			defer release()
			// the only thing we can do is log warn
			// this is already done in the functions
			newd.MarkServersExpired()
			newd.Servers(ctx) //nolint:errcheck
			newd.MarkOrganizationsExpired()
			newd.Organizations(ctx) //nolint:errcheck
			updorg, updsrv, err := newd.SecureHomeArgs(orgID)
			if err != nil {
				return "", err
			}
			ret := ReplaceWAYF(updsrv.AuthenticationURLTemplate, url, updorg.OrgID)
			return ret, nil
		},
		DisableAuthorize: disableAuth,
	}

	a, err := api.NewAPI(ctx, s.clientID, sd, s.cb, tok)
	if err != nil {
		return nil, err
	}

	sec := s.NewServer(orgID, server.TypeSecureInternet, a)
	return &sec, nil
}