Skip to content

Commit

Permalink
Add subscription data
Browse files Browse the repository at this point in the history
Signed-off-by: Savolro <[email protected]>
  • Loading branch information
Savolro committed Oct 17, 2024
1 parent b6685e4 commit 8e59632
Show file tree
Hide file tree
Showing 16 changed files with 623 additions and 83 deletions.
7 changes: 5 additions & 2 deletions ci/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ excluded_packages=$excluded_packages"\|events\/moose"
excluded_packages=$excluded_packages"\|pb\|magefiles"
excluded_categories="root,link,firewall,route,file,integration"

tags="internal"

# In case 'full' was specified, do not exclude anything and run
# everything
if [ "${1:-""}" = "full" ]; then
Expand All @@ -24,6 +26,7 @@ if [ "${1:-""}" = "full" ]; then

excluded_packages="thisshouldneverexist"
excluded_categories="root,link"
tags="internal,moose"
fi

# Execute tests in all the packages except the excluded ones
Expand All @@ -37,7 +40,7 @@ mkdir -p "${WORKDIR}"/coverage/unit
export LD_LIBRARY_PATH="${WORKDIR}/bin/deps/lib/amd64/latest"

# shellcheck disable=SC2046
go test -tags internal -v -race $(go list -buildvcs=false ./... | grep -v "${excluded_packages}") \
go test -tags "$tags" -v -race $(go list -tags "$tags" -buildvcs=false ./... | grep -v "${excluded_packages}") \
-coverprofile "${WORKDIR}"/coverage.txt \
-exclude "${excluded_categories}" \
-args -test.gocoverdir="${WORKDIR}/coverage/unit"
Expand All @@ -50,5 +53,5 @@ go tool cover -func="${WORKDIR}"/coverage.txt

if [ "${1:-""}" = "full" ]; then
# "gocover-cobertura" is used for test coverage visualization in the diff view.
gocover-cobertura < "$WORKDIR"/coverage.txt > coverage.xml
GOFLAGS=-tags="${tags}" gocover-cobertura < "$WORKDIR"/coverage.txt > coverage.xml
fi
63 changes: 33 additions & 30 deletions cmd/daemon/events_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,40 @@ import (

type dummyAnalytics struct{}

func (*dummyAnalytics) Enable() error { return nil }
func (*dummyAnalytics) Disable() error { return nil }
func (*dummyAnalytics) NotifyKillswitch(bool) error { return nil }
func (*dummyAnalytics) NotifyAutoconnect(bool) error { return nil }
func (*dummyAnalytics) NotifyDNS(events.DataDNS) error { return nil }
func (*dummyAnalytics) NotifyThreatProtectionLite(bool) error { return nil }
func (*dummyAnalytics) NotifyProtocol(config.Protocol) error { return nil }
func (*dummyAnalytics) NotifyAllowlist(events.DataAllowlist) error { return nil }
func (*dummyAnalytics) NotifyTechnology(config.Technology) error { return nil }
func (*dummyAnalytics) NotifyObfuscate(bool) error { return nil }
func (*dummyAnalytics) NotifyFirewall(bool) error { return nil }
func (*dummyAnalytics) NotifyRouting(bool) error { return nil }
func (*dummyAnalytics) NotifyNotify(bool) error { return nil }
func (*dummyAnalytics) NotifyMeshnet(bool) error { return nil }
func (*dummyAnalytics) NotifyIpv6(bool) error { return nil }
func (*dummyAnalytics) NotifyDefaults(any) error { return nil }
func (*dummyAnalytics) NotifyConnect(events.DataConnect) error { return nil }
func (*dummyAnalytics) NotifyDisconnect(events.DataDisconnect) error { return nil }
func (*dummyAnalytics) NotifyLogin(events.DataAuthorization) error { return nil }
func (*dummyAnalytics) NotifyLogout(events.DataAuthorization) error { return nil }
func (*dummyAnalytics) NotifyMFA(bool) error { return nil }
func (*dummyAnalytics) NotifyAccountCheck(core.ServicesResponse) error { return nil }
func (*dummyAnalytics) NotifyRequestAPI(events.DataRequestAPI) error { return nil }
func (*dummyAnalytics) NotifyUiItemsClick(events.UiItemsAction) error { return nil }
func (*dummyAnalytics) NotifyHeartBeat(int) error { return nil }
func (*dummyAnalytics) NotifyDeviceLocation(core.Insights) error { return nil }
func (*dummyAnalytics) NotifyLANDiscovery(bool) error { return nil }
func (*dummyAnalytics) NotifyVirtualLocation(bool) error { return nil }
func (*dummyAnalytics) NotifyPostquantumVpn(bool) error { return nil }
func (*dummyAnalytics) Enable() error { return nil }
func (*dummyAnalytics) Disable() error { return nil }
func (*dummyAnalytics) NotifyKillswitch(bool) error { return nil }
func (*dummyAnalytics) NotifyAutoconnect(bool) error { return nil }
func (*dummyAnalytics) NotifyDNS(events.DataDNS) error { return nil }
func (*dummyAnalytics) NotifyThreatProtectionLite(bool) error { return nil }
func (*dummyAnalytics) NotifyProtocol(config.Protocol) error { return nil }
func (*dummyAnalytics) NotifyAllowlist(events.DataAllowlist) error { return nil }
func (*dummyAnalytics) NotifyTechnology(config.Technology) error { return nil }
func (*dummyAnalytics) NotifyObfuscate(bool) error { return nil }
func (*dummyAnalytics) NotifyFirewall(bool) error { return nil }
func (*dummyAnalytics) NotifyRouting(bool) error { return nil }
func (*dummyAnalytics) NotifyNotify(bool) error { return nil }
func (*dummyAnalytics) NotifyMeshnet(bool) error { return nil }
func (*dummyAnalytics) NotifyIpv6(bool) error { return nil }
func (*dummyAnalytics) NotifyDefaults(any) error { return nil }
func (*dummyAnalytics) NotifyConnect(events.DataConnect) error { return nil }
func (*dummyAnalytics) NotifyDisconnect(events.DataDisconnect) error { return nil }
func (*dummyAnalytics) NotifyLogin(events.DataAuthorization) error { return nil }
func (*dummyAnalytics) NotifyLogout(events.DataAuthorization) error { return nil }
func (*dummyAnalytics) NotifyMFA(bool) error { return nil }
func (*dummyAnalytics) NotifyAccountCheck(any) error { return nil }
func (*dummyAnalytics) NotifyRequestAPI(events.DataRequestAPI) error { return nil }
func (*dummyAnalytics) NotifyUiItemsClick(events.UiItemsAction) error { return nil }
func (*dummyAnalytics) NotifyHeartBeat(int) error { return nil }
func (*dummyAnalytics) NotifyDeviceLocation(core.Insights) error { return nil }
func (*dummyAnalytics) NotifyLANDiscovery(bool) error { return nil }
func (*dummyAnalytics) NotifyVirtualLocation(bool) error { return nil }
func (*dummyAnalytics) NotifyPostquantumVpn(bool) error { return nil }

func newAnalytics(eventsDbPath string, fs *config.FilesystemConfigManager,
func newAnalytics(
eventsDbPath string,
fs *config.FilesystemConfigManager,
subAPI core.SubscriptionAPI,
version, env, id string) *dummyAnalytics {
return &dummyAnalytics{}
}
21 changes: 13 additions & 8 deletions cmd/daemon/events_moose.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"

"github.com/NordSecurity/nordvpn-linux/config"
"github.com/NordSecurity/nordvpn-linux/core"
"github.com/NordSecurity/nordvpn-linux/events/moose"
"github.com/NordSecurity/nordvpn-linux/internal"
)
Expand All @@ -15,7 +16,10 @@ var (
EventsSubdomain = ""
)

func newAnalytics(eventsDbPath string, fs *config.FilesystemConfigManager,
func newAnalytics(
eventsDbPath string,
fs *config.FilesystemConfigManager,
subAPI core.SubscriptionAPI,
ver, env, id string) *moose.Subscriber {
_ = os.Setenv("MOOSE_LOG_FILE", "Stdout")
logLevel := "error"
Expand All @@ -24,12 +28,13 @@ func newAnalytics(eventsDbPath string, fs *config.FilesystemConfigManager,
}
_ = os.Setenv("MOOSE_LOG", logLevel)
return &moose.Subscriber{
EventsDbPath: eventsDbPath,
Config: fs,
Version: ver,
Environment: env,
Domain: EventsDomain,
Subdomain: EventsSubdomain,
DeviceID: id,
EventsDbPath: eventsDbPath,
Config: fs,
Version: ver,
Environment: env,
Domain: EventsDomain,
Subdomain: EventsSubdomain,
DeviceID: id,
SubscriptionAPI: subAPI,
}
}
2 changes: 1 addition & 1 deletion cmd/daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ func main() {
// obfuscated machineID
deviceID := fmt.Sprintf("%x", sha256.Sum256([]byte(cfg.MachineID.String()+Salt)))

analytics := newAnalytics(eventsDbPath, fsystem, Version, Environment, deviceID)
analytics := newAnalytics(eventsDbPath, fsystem, defaultAPI, Version, Environment, deviceID)
if cfg.Analytics.Get() {
if err := analytics.Enable(); err != nil {
log.Println(internal.WarningPrefix, err)
Expand Down
32 changes: 32 additions & 0 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ type CombinedAPI interface {
CreateUser(email, password string) (*UserCreateResponse, error)
}

// SubscriptionAPI is responsible for fetching the subscription data of the user
type SubscriptionAPI interface {
// Orders returns a list of orders done by the user
Orders(token string) ([]Order, error)
// Payments returns a list of payments done by the user
Payments(token string) ([]PaymentResponse, error)
}

type DefaultAPI struct {
agent string
baseURL string
Expand Down Expand Up @@ -531,3 +539,27 @@ func (api *DefaultAPI) Logout(token string) error {

return nil
}

func (api *DefaultAPI) Orders(token string) ([]Order, error) {
return getData[[]Order](api, token, urlOrders)
}

func (api *DefaultAPI) Payments(token string) ([]PaymentResponse, error) {
return getData[[]PaymentResponse](api, token, urlPayments)
}

// getData calls a HTTP get request for the endpoints requiring authentication and returns the
// requested data.
func getData[T any](api *DefaultAPI, token string, url string) (T, error) {
var data T
resp, err := api.request(url, http.MethodGet, nil, token)
if err != nil {
return data, fmt.Errorf("executing HTTP GET request: %w", err)
}
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return data, fmt.Errorf("decoding data from JSON: %w", err)
}

return data, nil
}
67 changes: 64 additions & 3 deletions core/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"fmt"
"log"
"net/netip"
"strconv"
"strings"
"time"

"golang.org/x/exp/slices"

Expand Down Expand Up @@ -142,12 +144,73 @@ type CurrentUserResponse struct {
Email string `json:"email"`
}

type Order struct {
ID int `json:"id,omitempty"`
RemoteID int `json:"remote_id,omitempty"`
Status string `json:"status,omitempty"`
Plans Plans `json:"plans,omitempty"`
}

type PaymentResponse struct {
Payment Payment `json:"payment,omitempty"`
}

type Payment struct {
CreatedAt time.Time `json:"created_at,omitempty"`
Subscription Subscription `json:"subscription,omitempty"`
Status string `json:"status,omitempty"`
Payer Payer `json:"payer,omitempty"`
Amount float32 `json:"amount,omitempty"`
Currency string `json:"currency,omitempty"`
Provider string `json:"provider,omitempty"`
}

func (p *Payment) UnmarshalJSON(data []byte) error {
type Alias Payment
aux := &struct {
CreatedAt string `json:"created_at"`
Amount string `json:"amount"`
*Alias
}{
Alias: (*Alias)(p),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
t, err := time.Parse(internal.ServerDateFormat, aux.CreatedAt)
if err != nil {
return fmt.Errorf("parsing created_at: %w", err)
}

amount, err := strconv.ParseFloat(aux.Amount, 32)
if err != nil {
return fmt.Errorf("parsing amount: %w", err)
}

p.CreatedAt = t
p.Amount = float32(amount)
return nil
}

type Subscription struct {
MerchantID int32 `json:"merchant_id,omitempty"`
FrequencyInterval int32 `json:"frequency_interval,omitempty"`
FrequencyUnit string `json:"frequency_unit,omitempty"`
Status string `json:"status,omitempty"`
}

type Payer struct {
OrderID int `json:"order_id,omitempty"`
}

type TokenRenewResponse struct {
Token string `json:"token"`
RenewToken string `json:"renew_token"`
ExpiresAt string `json:"expires_at"`
}

type Plans []Plan

type TrustedPassTokenResponse struct {
OwnerID string `json:"owner_id"`
Token string `json:"token"`
Expand All @@ -157,10 +220,8 @@ type MultifactorAuthStatusResponse struct {
Status string `json:"status"`
}

type Plans []Plan

type Plan struct {
ID int64 `json:"id"`
ID int32 `json:"id"`
Identifier string `json:"identifier"`
Type string `json:"type"`
Title string `json:"title"`
Expand Down
47 changes: 47 additions & 0 deletions core/models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package core
import (
"encoding/json"
"net/netip"
"reflect"
"strconv"
"strings"
"testing"
"time"

"github.com/NordSecurity/nordvpn-linux/config"
"github.com/NordSecurity/nordvpn-linux/test/category"
Expand Down Expand Up @@ -724,3 +727,47 @@ func TestLocationsCountry(t *testing.T) {
assert.EqualValues(t, first, country)
})
}

func TestPayment_UnmarshalJSON(t *testing.T) {
category.Set(t, category.Unit)
for _, tt := range []struct {
name string
json string
payment Payment
errType error
}{
{
name: "valid payment",
json: `{"created_at": "2001-01-01 00:00:00", "amount": "1.23", "status": "done"}`,
payment: Payment{
CreatedAt: time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC),
Amount: 1.23,
Status: "done",
},
},
{
name: "invalid JSON",
errType: &json.SyntaxError{},
},
{
name: "invalid created_at",
errType: &time.ParseError{},
json: `{"created_at": "2001-01-01"}`,
},
{
name: "invalid amount",
errType: strconv.ErrSyntax,
json: `{"created_at": "2001-01-01 00:00:00", "amount": "1.2.3"}`,
},
} {
t.Run(tt.name, func(t *testing.T) {
var p Payment
err := p.UnmarshalJSON([]byte(tt.json))
if tt.errType != nil {
target := reflect.New(reflect.TypeOf(tt.errType)).Interface()
assert.ErrorAs(t, err, target)
}
assert.Equal(t, tt.payment, p)
})
}
}
6 changes: 6 additions & 0 deletions core/urls.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ const (
// ServicesURL defines url to check user's current/expired services
ServicesURL = UsersURL + "/services"

// urlOrders defines URL to list user's orders
urlOrders = UsersURL + "/orders"

// urlOrders defines URL to list user's payments
urlPayments = UsersURL + "/payments"

// CredentialsURL defines url to generate openvpn credentials
CredentialsURL = ServicesURL + "/credentials"

Expand Down
Loading

0 comments on commit 8e59632

Please sign in to comment.