From 7af07c596166bf93b79a9d0816b1950dde360fb9 Mon Sep 17 00:00:00 2001 From: jwijenbergh Date: Fri, 17 Jun 2022 14:00:40 +0200 Subject: Server: Implement function for checking renewal button visibility --- internal/server/api.go | 221 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 internal/server/api.go (limited to 'internal/server/api.go') diff --git a/internal/server/api.go b/internal/server/api.go new file mode 100644 index 0000000..96bd641 --- /dev/null +++ b/internal/server/api.go @@ -0,0 +1,221 @@ +package server + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + httpw "github.com/jwijenbergh/eduvpn-common/internal/http" + "github.com/jwijenbergh/eduvpn-common/internal/log" + "github.com/jwijenbergh/eduvpn-common/internal/util" +) + +func APIGetEndpoints(baseURL string) (*ServerEndpoints, error) { + url := fmt.Sprintf("%s/%s", baseURL, WellKnownPath) + _, body, bodyErr := httpw.HTTPGet(url) + + if bodyErr != nil { + return nil, &APIGetEndpointsError{Err: bodyErr} + } + + endpoints := &ServerEndpoints{} + jsonErr := json.Unmarshal(body, endpoints) + + if jsonErr != nil { + return nil, &APIGetEndpointsError{Err: jsonErr} + } + + return endpoints, nil +} + +// Authorized wrappers on top of HTTP +// the errors will not be wrapped here so that the caller can check if we got a status error, to retry oauth +func apiAuthorized(server Server, method string, endpoint string, opts *httpw.HTTPOptionalParams) (http.Header, []byte, error) { + // Ensure optional is not nil as we will fill it with headers + if opts == nil { + opts = &httpw.HTTPOptionalParams{} + } + base, baseErr := server.GetBase() + + if baseErr != nil { + return nil, nil, baseErr + } + + url := base.Endpoints.API.V3.API + endpoint + + // Ensure we have valid tokens + stateBefore := base.FSM.Current + oauthErr := EnsureTokens(server) + + // we reset the state so that we go from the authorized state to the state we want + base.FSM.Current = stateBefore + + if oauthErr != nil { + return nil, nil, oauthErr + } + + headerKey := "Authorization" + headerValue := fmt.Sprintf("Bearer %s", server.GetOAuth().Token.Access) + if opts.Headers != nil { + opts.Headers.Add(headerKey, headerValue) + } else { + opts.Headers = http.Header{headerKey: {headerValue}} + } + return httpw.HTTPMethodWithOpts(method, url, opts) +} + +func apiAuthorizedRetry(server Server, method string, endpoint string, opts *httpw.HTTPOptionalParams) (http.Header, []byte, error) { + header, body, bodyErr := apiAuthorized(server, method, endpoint, opts) + base, baseErr := server.GetBase() + + if baseErr != nil { + return nil, nil, &APIAuthorizedError{Err: baseErr} + } + if bodyErr != nil { + var error *httpw.HTTPStatusError + + // Only retry authorized if we get a HTTP 401 + if errors.As(bodyErr, &error) && error.Status == 401 { + base.Logger.Log(log.LOG_INFO, fmt.Sprintf("API: Got HTTP error %v, retrying authorized", error)) + // Tell the method that the token is expired + server.GetOAuth().Token.ExpiredTimestamp = util.GenerateTimeSeconds() + retryHeader, retryBody, retryErr := apiAuthorized(server, method, endpoint, opts) + if retryErr != nil { + return nil, nil, &APIAuthorizedError{Err: retryErr} + } + return retryHeader, retryBody, nil + } + return nil, nil, &APIAuthorizedError{Err: bodyErr} + } + return header, body, nil +} + +func APIInfo(server Server) error { + _, body, bodyErr := apiAuthorizedRetry(server, http.MethodGet, "/info", nil) + if bodyErr != nil { + return &APIInfoError{Err: bodyErr} + } + structure := ServerProfileInfo{} + jsonErr := json.Unmarshal(body, &structure) + + if jsonErr != nil { + return &APIInfoError{Err: jsonErr} + } + + base, baseErr := server.GetBase() + + if baseErr != nil { + return &APIInfoError{Err: baseErr} + } + + // Store the profiles and make sure that the current profile is not overwritten + previousProfile := base.Profiles.Current + base.Profiles = structure + base.Profiles.Current = previousProfile + base.ProfilesRaw = string(body) + return nil +} + +func APIConnectWireguard(server Server, profile_id string, pubkey string, supportsOpenVPN bool) (string, string, int64, error) { + headers := http.Header{ + "content-type": {"application/x-www-form-urlencoded"}, + "accept": {"application/x-wireguard-profile"}, + } + + if supportsOpenVPN { + headers.Add("accept", "application/x-openvpn-profile") + } + + urlForm := url.Values{ + "profile_id": {profile_id}, + "public_key": {pubkey}, + } + header, connectBody, connectErr := apiAuthorizedRetry(server, http.MethodPost, "/connect", &httpw.HTTPOptionalParams{Headers: headers, Body: urlForm}) + if connectErr != nil { + return "", "", 0, &APIConnectWireguardError{Err: connectErr} + } + + expires := header.Get("expires") + + pTime, pTimeErr := http.ParseTime(expires) + if pTimeErr != nil { + return "", "", 0, &APIConnectWireguardError{Err: pTimeErr} + } + + contentType := header.Get("content-type") + + content := "openvpn" + if contentType == "application/x-wireguard-profile" { + content = "wireguard" + } + return string(connectBody), content, pTime.Unix(), nil +} + +func APIConnectOpenVPN(server Server, profile_id string) (string, int64, error) { + headers := http.Header{ + "content-type": {"application/x-www-form-urlencoded"}, + "accept": {"application/x-openvpn-profile"}, + } + + urlForm := url.Values{ + "profile_id": {profile_id}, + } + + header, connectBody, connectErr := apiAuthorizedRetry(server, http.MethodPost, "/connect", &httpw.HTTPOptionalParams{Headers: headers, Body: urlForm}) + if connectErr != nil { + return "", 0, &APIConnectOpenVPNError{Err: connectErr} + } + + expires := header.Get("expires") + pTime, pTimeErr := http.ParseTime(expires) + if pTimeErr != nil { + return "", 0, &APIConnectOpenVPNError{Err: pTimeErr} + } + return string(connectBody), pTime.Unix(), nil +} + +// This needs no further return value as it's best effort +func APIDisconnect(server Server) { + apiAuthorizedRetry(server, http.MethodPost, "/disconnect", nil) +} + +type APIAuthorizedError struct { + Err error +} + +func (e *APIAuthorizedError) Error() string { + return fmt.Sprintf("failed api authorized call with error: %v", e.Err) +} + +type APIConnectWireguardError struct { + Err error +} + +func (e *APIConnectWireguardError) Error() string { + return fmt.Sprintf("failed api /connect wireguard call with error: %v", e.Err) +} + +type APIConnectOpenVPNError struct { + Err error +} + +func (e *APIConnectOpenVPNError) Error() string { + return fmt.Sprintf("failed api /connect OpenVPN call with error: %v", e.Err) +} + +type APIInfoError struct { + Err error +} + +func (e *APIInfoError) Error() string { + return fmt.Sprintf("failed api /info call with error: %v", e.Err) +} + +type APIGetEndpointsError struct { + Err error +} + +func (e *APIGetEndpointsError) Error() string { + return fmt.Sprintf("failed to get server endpoint with error %v", e.Err) +} -- cgit v1.2.3