mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-25 16:18:49 +01:00
OpenAPI specification and API Adjusts (#356)
Introduced an OpenAPI specification. Updated API handlers to use the specification types. Added patch operation for rules and groups and methods to the account manager. HTTP PUT operations require id, fail if not provided. Use snake_case for HTTP request and response body
This commit is contained in:
parent
a454a1aa28
commit
503a116f7c
11
go.mod
11
go.mod
@ -17,8 +17,8 @@ require (
|
||||
github.com/spf13/cobra v1.3.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211209221555-9c9e7e272434
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
||||
golang.zx2c4.com/wireguard/windows v0.5.1
|
||||
@ -84,6 +84,7 @@ require (
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.33.0 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
||||
@ -91,15 +92,15 @@ require (
|
||||
github.com/yuin/goldmark v1.4.1 // indirect
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect
|
||||
golang.org/x/net v0.0.0-20220513224357-95641704303c // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
||||
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
23
go.sum
23
go.sum
@ -461,7 +461,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
@ -509,6 +508,7 @@ github.com/pion/turn/v2 v2.0.7 h1:SZhc00WDovK6czaN1RSiHqbwANtIO6wfZQsU0m0KNE8=
|
||||
github.com/pion/turn/v2 v2.0.7/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@ -548,7 +548,8 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so=
|
||||
github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM=
|
||||
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
||||
@ -646,8 +647,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -754,8 +756,8 @@ golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0=
|
||||
golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -887,8 +889,8 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -971,8 +973,9 @@ golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d h1:9+v0G0naRhLPOJEeJOL6NuXTtAHHwmkyZlgQJ0XcQ8I=
|
||||
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
@ -1138,8 +1141,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
cacheStore "github.com/eko/gocache/v2/store"
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
gocache "github.com/patrickmn/go-cache"
|
||||
"github.com/rs/xid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -35,7 +34,7 @@ type AccountManager interface {
|
||||
accountId string,
|
||||
keyName string,
|
||||
keyType SetupKeyType,
|
||||
expiresIn *util.Duration,
|
||||
expiresIn time.Duration,
|
||||
) (*SetupKey, error)
|
||||
RevokeSetupKey(accountId string, keyId string) (*SetupKey, error)
|
||||
RenameSetupKey(accountId string, keyId string, newName string) (*SetupKey, error)
|
||||
@ -55,6 +54,7 @@ type AccountManager interface {
|
||||
GetUsersFromAccount(accountId string) ([]*UserInfo, error)
|
||||
GetGroup(accountId, groupID string) (*Group, error)
|
||||
SaveGroup(accountId string, group *Group) error
|
||||
UpdateGroup(accountID string, groupID string, operations []GroupUpdateOperation) (*Group, error)
|
||||
DeleteGroup(accountId, groupID string) error
|
||||
ListGroups(accountId string) ([]*Group, error)
|
||||
GroupAddPeer(accountId, groupID, peerKey string) error
|
||||
@ -62,6 +62,7 @@ type AccountManager interface {
|
||||
GroupListPeers(accountId, groupID string) ([]*Peer, error)
|
||||
GetRule(accountId, ruleID string) (*Rule, error)
|
||||
SaveRule(accountID string, rule *Rule) error
|
||||
UpdateRule(accountID string, ruleID string, operations []RuleUpdateOperation) (*Rule, error)
|
||||
DeleteRule(accountId, ruleID string) error
|
||||
ListRules(accountId string) ([]*Rule, error)
|
||||
}
|
||||
@ -222,14 +223,14 @@ func (am *DefaultAccountManager) AddSetupKey(
|
||||
accountId string,
|
||||
keyName string,
|
||||
keyType SetupKeyType,
|
||||
expiresIn *util.Duration,
|
||||
expiresIn time.Duration,
|
||||
) (*SetupKey, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
keyDuration := DefaultSetupKeyDuration
|
||||
if expiresIn != nil {
|
||||
keyDuration = expiresIn.Duration
|
||||
if expiresIn != 0 {
|
||||
keyDuration = expiresIn
|
||||
}
|
||||
|
||||
account, err := am.Store.GetAccount(accountId)
|
||||
@ -633,7 +634,9 @@ func addAllGroup(account *Account) {
|
||||
|
||||
defaultRule := &Rule{
|
||||
ID: xid.New().String(),
|
||||
Name: "Default",
|
||||
Name: DefaultRuleName,
|
||||
Description: DefaultRuleDescription,
|
||||
Disabled: false,
|
||||
Source: []string{allGroup.ID},
|
||||
Destination: []string{allGroup.ID},
|
||||
}
|
||||
@ -687,3 +690,19 @@ func getAccountSetupKeyByKey(acc *Account, key string) *SetupKey {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeFromList(inputList []string, toRemove []string) []string {
|
||||
toRemoveMap := make(map[string]struct{})
|
||||
for _, item := range toRemove {
|
||||
toRemoveMap[item] = struct{}{}
|
||||
}
|
||||
|
||||
var resultList []string
|
||||
for _, item := range inputList {
|
||||
_, ok := toRemoveMap[item]
|
||||
if !ok {
|
||||
resultList = append(resultList, item)
|
||||
}
|
||||
}
|
||||
return resultList
|
||||
}
|
||||
|
@ -184,8 +184,8 @@ func (s *FileStore) DeletePeer(accountId string, peerKey string) (*Peer, error)
|
||||
delete(s.PeerKeyId2SrcRulesId, peerKey)
|
||||
|
||||
// cleanup groups
|
||||
var peers []string
|
||||
for _, g := range account.Groups {
|
||||
var peers []string
|
||||
for _, p := range g.Peers {
|
||||
if p != peerKey {
|
||||
peers = append(peers, p)
|
||||
|
@ -17,6 +17,26 @@ type Group struct {
|
||||
Peers []string
|
||||
}
|
||||
|
||||
const (
|
||||
// UpdateGroupName indicates a name update operation
|
||||
UpdateGroupName GroupUpdateOperationType = iota
|
||||
// InsertPeersToGroup indicates insert peers to group operation
|
||||
InsertPeersToGroup
|
||||
// RemovePeersFromGroup indicates a remove peers from group operation
|
||||
RemovePeersFromGroup
|
||||
// UpdateGroupPeers indicates a replacement of group peers list
|
||||
UpdateGroupPeers
|
||||
)
|
||||
|
||||
// GroupUpdateOperationType operation type
|
||||
type GroupUpdateOperationType int
|
||||
|
||||
// GroupUpdateOperation operation object with type and values to be applied
|
||||
type GroupUpdateOperation struct {
|
||||
Type GroupUpdateOperationType
|
||||
Values []string
|
||||
}
|
||||
|
||||
func (g *Group) Copy() *Group {
|
||||
return &Group{
|
||||
ID: g.ID,
|
||||
@ -63,6 +83,56 @@ func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error
|
||||
return am.updateAccountPeers(account)
|
||||
}
|
||||
|
||||
// UpdateGroup updates a group using a list of operations
|
||||
func (am *DefaultAccountManager) UpdateGroup(accountID string,
|
||||
groupID string, operations []GroupUpdateOperation) (*Group, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||
}
|
||||
|
||||
groupToUpdate, ok := account.Groups[groupID]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "group %s no longer exists", groupID)
|
||||
}
|
||||
|
||||
group := groupToUpdate.Copy()
|
||||
|
||||
for _, operation := range operations {
|
||||
switch operation.Type {
|
||||
case UpdateGroupName:
|
||||
group.Name = operation.Values[0]
|
||||
case UpdateGroupPeers:
|
||||
group.Peers = operation.Values
|
||||
case InsertPeersToGroup:
|
||||
sourceList := group.Peers
|
||||
resultList := removeFromList(sourceList, operation.Values)
|
||||
group.Peers = append(resultList, operation.Values...)
|
||||
case RemovePeersFromGroup:
|
||||
sourceList := group.Peers
|
||||
resultList := removeFromList(sourceList, operation.Values)
|
||||
group.Peers = resultList
|
||||
}
|
||||
}
|
||||
|
||||
account.Groups[groupID] = group
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = am.updateAccountPeers(account)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to update account peers")
|
||||
}
|
||||
|
||||
return group, nil
|
||||
}
|
||||
|
||||
// DeleteGroup object of the peers
|
||||
func (am *DefaultAccountManager) DeleteGroup(accountID, groupID string) error {
|
||||
am.mux.Lock()
|
||||
|
5
management/server/http/api/cfg.yaml
Normal file
5
management/server/http/api/cfg.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
package: api
|
||||
generate:
|
||||
models: true
|
||||
embedded-spec: false
|
||||
output: types.gen.go
|
16
management/server/http/api/generate.sh
Normal file
16
management/server/http/api/generate.sh
Normal file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ! which realpath > /dev/null 2>&1
|
||||
then
|
||||
echo realpath is not installed
|
||||
echo run: brew install coreutils
|
||||
exit 1
|
||||
fi
|
||||
|
||||
old_pwd=$(pwd)
|
||||
script_path=$(dirname $(realpath "$0"))
|
||||
cd "$script_path"
|
||||
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.11.0
|
||||
oapi-codegen --config cfg.yaml openapi.yml
|
||||
cd "$old_pwd"
|
942
management/server/http/api/openapi.yml
Normal file
942
management/server/http/api/openapi.yml
Normal file
@ -0,0 +1,942 @@
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
title: NetBird REST API
|
||||
description: API to manipulate groups, rules and retrieve information about peers and users
|
||||
version: 0.0.1
|
||||
tags:
|
||||
- name: Users
|
||||
description: Interact with and view information about users.
|
||||
- name: Peers
|
||||
description: Interact with and view information about peers.
|
||||
- name: Setup Keys
|
||||
description: Interact with and view information about setup keys.
|
||||
- name: Groups
|
||||
description: Interact with and view information about groups.
|
||||
- name: Rules
|
||||
description: Interact with and view information about rules.
|
||||
components:
|
||||
schemas:
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: User ID
|
||||
type: string
|
||||
email:
|
||||
description: User's email address
|
||||
type: string
|
||||
name:
|
||||
description: User's name from idp provider
|
||||
type: string
|
||||
role:
|
||||
description: User's Netbird account role
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- email
|
||||
- name
|
||||
- role
|
||||
PeerMinimum:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: Peer ID
|
||||
type: string
|
||||
name:
|
||||
description: Peer's hostname
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
Peer:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PeerMinimum'
|
||||
- type: object
|
||||
properties:
|
||||
ip:
|
||||
description: Peer's IP address
|
||||
type: string
|
||||
connected:
|
||||
description: Peer to Management connection status
|
||||
type: boolean
|
||||
last_seen:
|
||||
description: Last time peer connected to Netbird's management service
|
||||
type: string
|
||||
format: date-time
|
||||
os:
|
||||
description: Peer's operating system and version
|
||||
type: string
|
||||
version:
|
||||
description: Peer's daemon or cli version
|
||||
type: string
|
||||
groups:
|
||||
description: Groups that the peer belongs to
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/GroupMinimum'
|
||||
activated_by:
|
||||
description: Provides information of who activated the Peer. User or Setup Key
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- value
|
||||
required:
|
||||
- ip
|
||||
- connected
|
||||
- last_seen
|
||||
- os
|
||||
- version
|
||||
- groups
|
||||
- activated_by
|
||||
SetupKey:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: Setup Key ID
|
||||
type: string
|
||||
key:
|
||||
description: Setup Key value
|
||||
type: string
|
||||
name:
|
||||
description: Setup key name identifier
|
||||
type: string
|
||||
expires:
|
||||
description: Setup Key expiration date
|
||||
type: string
|
||||
format: date-time
|
||||
type:
|
||||
description: Setup key type, one-off for single time usage and reusable
|
||||
type: string
|
||||
valid:
|
||||
description: Setup key validity status
|
||||
type: boolean
|
||||
revoked:
|
||||
description: Setup key revocation status
|
||||
type: boolean
|
||||
used_times:
|
||||
description: Usage count of setup key
|
||||
type: integer
|
||||
last_used:
|
||||
description: Setup key last usage date
|
||||
type: string
|
||||
format: date-time
|
||||
state:
|
||||
description: Setup key status, "valid", "overused","expired" or "revoked"
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- key
|
||||
- name
|
||||
- expires
|
||||
- type
|
||||
- valid
|
||||
- revoked
|
||||
- used_times
|
||||
- last_used
|
||||
- state
|
||||
SetupKeyRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: Setup Key name
|
||||
type: string
|
||||
type:
|
||||
description: Setup key type, one-off for single time usage and reusable
|
||||
type: string
|
||||
expires_in:
|
||||
description: Expiration time in seconds
|
||||
type: integer
|
||||
revoked:
|
||||
description: Setup key revocation status
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- expires_in
|
||||
- revoked
|
||||
GroupMinimum:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: Group ID
|
||||
type: string
|
||||
name:
|
||||
description: Group Name identifier
|
||||
type: string
|
||||
peers_count:
|
||||
description: Count of peers associated to the group
|
||||
type: integer
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- peers_count
|
||||
Group:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/GroupMinimum'
|
||||
- type: object
|
||||
properties:
|
||||
peers:
|
||||
description: List of peers object
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PeerMinimum'
|
||||
required:
|
||||
- peers
|
||||
GroupPatchOperation:
|
||||
type: object
|
||||
properties:
|
||||
op:
|
||||
description: Patch operation type
|
||||
type: string
|
||||
enum: [ "replace","add","remove" ]
|
||||
path:
|
||||
description: Group field to update in form /<field>
|
||||
type: string
|
||||
enum: [ "name","peers" ]
|
||||
value:
|
||||
description: Values to be applied
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- op
|
||||
- path
|
||||
- value
|
||||
RuleMinimum:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: Rule name identifier
|
||||
type: string
|
||||
description:
|
||||
description: Rule friendly description
|
||||
type: string
|
||||
disabled:
|
||||
description: Rules status
|
||||
type: boolean
|
||||
flow:
|
||||
description: Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- description
|
||||
- disabled
|
||||
- flow
|
||||
Rule:
|
||||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
id:
|
||||
description: Rule ID
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- $ref: '#/components/schemas/RuleMinimum'
|
||||
- type: object
|
||||
properties:
|
||||
sources:
|
||||
description: Rule source groups
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/GroupMinimum'
|
||||
destinations:
|
||||
description: Rule destination groups
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/GroupMinimum'
|
||||
required:
|
||||
- sources
|
||||
- destinations
|
||||
RulePatchOperation:
|
||||
type: object
|
||||
properties:
|
||||
op:
|
||||
description: Patch operation type
|
||||
type: string
|
||||
enum: [ "replace","add","remove" ]
|
||||
path:
|
||||
description: Rule field to update in form /<field>
|
||||
type: string
|
||||
enum: [ "name","description","disabled","flow","sources","destinations" ]
|
||||
value:
|
||||
description: Values to be applied
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- op
|
||||
- path
|
||||
- value
|
||||
responses:
|
||||
not_found:
|
||||
description: Resource not found
|
||||
content: {}
|
||||
validation_failed_simple:
|
||||
description: Validation failed
|
||||
content: {}
|
||||
bad_request:
|
||||
description: Bad Request
|
||||
content: {}
|
||||
internal_error:
|
||||
description: Internal Server Error
|
||||
content: { }
|
||||
validation_failed:
|
||||
description: Validation failed
|
||||
content: {}
|
||||
forbidden:
|
||||
description: Forbidden
|
||||
content: {}
|
||||
requires_authentication:
|
||||
description: Requires authentication
|
||||
content: {}
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
paths:
|
||||
/api/users:
|
||||
get:
|
||||
summary: Returns a list of all users
|
||||
tags: [Users]
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON array of Users
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/peers:
|
||||
get:
|
||||
summary: Returns a list of all peers
|
||||
tags: [Peers]
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of Peers
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Peer'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/peers/{id}:
|
||||
get:
|
||||
summary: Get information about a peer
|
||||
tags: [Peers]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Peer ID
|
||||
responses:
|
||||
'200':
|
||||
description: A Peer object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Peer'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update information about a peer
|
||||
tags: [Peers]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Peer ID
|
||||
requestBody:
|
||||
description: update to peers
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
responses:
|
||||
'200':
|
||||
description: A Peer object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Peer'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a peer
|
||||
tags: [Peers]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Peer ID
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/setup-keys:
|
||||
get:
|
||||
summary: Returns a list of all Setup Keys
|
||||
tags: [Setup Keys]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of Setup keys
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SetupKey'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
summary: Creates a Setup Key
|
||||
tags: [Setup Keys]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
description: New Setup Key request
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: '#/components/schemas/SetupKeyRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: A Setup Keys Object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SetupKey'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/setup-keys/{id}:
|
||||
get:
|
||||
summary: Get information about a Setup Key
|
||||
tags: [Setup Keys]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Setup Key ID
|
||||
responses:
|
||||
'200':
|
||||
description: A Setup Key object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SetupKey'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update information about a Setup Key
|
||||
tags: [Setup Keys]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Setup Key ID
|
||||
requestBody:
|
||||
description: update to Setup Key
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: '#/components/schemas/SetupKeyRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: A Setup Key object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SetupKey'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a Setup Key
|
||||
tags: [Setup Keys]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Setup Key ID
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/groups:
|
||||
get:
|
||||
summary: Returns a list of all Groups
|
||||
tags: [Groups]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of Groups
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Group'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
summary: Creates a Group
|
||||
tags: [Groups]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
description: New Group request
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
peers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
responses:
|
||||
'200':
|
||||
description: A Group Object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Group'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/groups/{id}:
|
||||
get:
|
||||
summary: Get information about a Group
|
||||
tags: [Groups]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Group ID
|
||||
responses:
|
||||
'200':
|
||||
description: A Group object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Group'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update/Replace a Group
|
||||
tags: [Groups]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Group ID
|
||||
requestBody:
|
||||
description: Update Group request
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
Name:
|
||||
type: string
|
||||
Peers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: A Group object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Group'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
patch:
|
||||
summary: Update information about a Group
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Group ID
|
||||
requestBody:
|
||||
description: Update Group request using a list of json patch objects
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/GroupPatchOperation'
|
||||
responses:
|
||||
'200':
|
||||
description: A Group object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Group'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a Group
|
||||
tags: [Groups]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Group ID
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/rules:
|
||||
get:
|
||||
summary: Returns a list of all Rules
|
||||
tags: [Rules]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of Rules
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Rule'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
summary: Creates a Rule
|
||||
tags: [Rules]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
description: New Rule request
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuleMinimum'
|
||||
- type: object
|
||||
properties:
|
||||
sources:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
destinations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: A Rule Object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rule'
|
||||
/api/rules/{id}:
|
||||
get:
|
||||
summary: Get information about a Rules
|
||||
tags: [Rules]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Rule ID
|
||||
responses:
|
||||
'200':
|
||||
description: A Rule object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rule'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update/Replace a Rule
|
||||
tags: [Rules]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Rule ID
|
||||
requestBody:
|
||||
description: Update Rule request
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuleMinimum'
|
||||
- type: object
|
||||
properties:
|
||||
sources:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
destinations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: A Rule object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rule'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
patch:
|
||||
summary: Update information about a Rule
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Rule ID
|
||||
requestBody:
|
||||
description: Update Rule request using a list of json patch objects
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RulePatchOperation'
|
||||
responses:
|
||||
'200':
|
||||
description: A Rule object
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Rule'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a Rule
|
||||
tags: [Rules]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The Rule ID
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
339
management/server/http/api/types.gen.go
Normal file
339
management/server/http/api/types.gen.go
Normal file
@ -0,0 +1,339 @@
|
||||
// Package api provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/deepmap/oapi-codegen version v1.11.0 DO NOT EDIT.
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
BearerAuthScopes = "BearerAuth.Scopes"
|
||||
)
|
||||
|
||||
// Defines values for GroupPatchOperationOp.
|
||||
const (
|
||||
GroupPatchOperationOpAdd GroupPatchOperationOp = "add"
|
||||
GroupPatchOperationOpRemove GroupPatchOperationOp = "remove"
|
||||
GroupPatchOperationOpReplace GroupPatchOperationOp = "replace"
|
||||
)
|
||||
|
||||
// Defines values for GroupPatchOperationPath.
|
||||
const (
|
||||
GroupPatchOperationPathName GroupPatchOperationPath = "name"
|
||||
GroupPatchOperationPathPeers GroupPatchOperationPath = "peers"
|
||||
)
|
||||
|
||||
// Defines values for RulePatchOperationOp.
|
||||
const (
|
||||
RulePatchOperationOpAdd RulePatchOperationOp = "add"
|
||||
RulePatchOperationOpRemove RulePatchOperationOp = "remove"
|
||||
RulePatchOperationOpReplace RulePatchOperationOp = "replace"
|
||||
)
|
||||
|
||||
// Defines values for RulePatchOperationPath.
|
||||
const (
|
||||
RulePatchOperationPathDescription RulePatchOperationPath = "description"
|
||||
RulePatchOperationPathDestinations RulePatchOperationPath = "destinations"
|
||||
RulePatchOperationPathDisabled RulePatchOperationPath = "disabled"
|
||||
RulePatchOperationPathFlow RulePatchOperationPath = "flow"
|
||||
RulePatchOperationPathName RulePatchOperationPath = "name"
|
||||
RulePatchOperationPathSources RulePatchOperationPath = "sources"
|
||||
)
|
||||
|
||||
// Group defines model for Group.
|
||||
type Group struct {
|
||||
// Group ID
|
||||
Id string `json:"id"`
|
||||
|
||||
// Group Name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// List of peers object
|
||||
Peers []PeerMinimum `json:"peers"`
|
||||
|
||||
// Count of peers associated to the group
|
||||
PeersCount int `json:"peers_count"`
|
||||
}
|
||||
|
||||
// GroupMinimum defines model for GroupMinimum.
|
||||
type GroupMinimum struct {
|
||||
// Group ID
|
||||
Id string `json:"id"`
|
||||
|
||||
// Group Name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Count of peers associated to the group
|
||||
PeersCount int `json:"peers_count"`
|
||||
}
|
||||
|
||||
// GroupPatchOperation defines model for GroupPatchOperation.
|
||||
type GroupPatchOperation struct {
|
||||
// Patch operation type
|
||||
Op GroupPatchOperationOp `json:"op"`
|
||||
|
||||
// Group field to update in form /<field>
|
||||
Path GroupPatchOperationPath `json:"path"`
|
||||
|
||||
// Values to be applied
|
||||
Value []string `json:"value"`
|
||||
}
|
||||
|
||||
// Patch operation type
|
||||
type GroupPatchOperationOp string
|
||||
|
||||
// Group field to update in form /<field>
|
||||
type GroupPatchOperationPath string
|
||||
|
||||
// Peer defines model for Peer.
|
||||
type Peer struct {
|
||||
// Provides information of who activated the Peer. User or Setup Key
|
||||
ActivatedBy struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
} `json:"activated_by"`
|
||||
|
||||
// Peer to Management connection status
|
||||
Connected bool `json:"connected"`
|
||||
|
||||
// Groups that the peer belongs to
|
||||
Groups []GroupMinimum `json:"groups"`
|
||||
|
||||
// Peer ID
|
||||
Id string `json:"id"`
|
||||
|
||||
// Peer's IP address
|
||||
Ip string `json:"ip"`
|
||||
|
||||
// Last time peer connected to Netbird's management service
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
|
||||
// Peer's hostname
|
||||
Name string `json:"name"`
|
||||
|
||||
// Peer's operating system and version
|
||||
Os string `json:"os"`
|
||||
|
||||
// Peer's daemon or cli version
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// PeerMinimum defines model for PeerMinimum.
|
||||
type PeerMinimum struct {
|
||||
// Peer ID
|
||||
Id string `json:"id"`
|
||||
|
||||
// Peer's hostname
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Rule defines model for Rule.
|
||||
type Rule struct {
|
||||
// Rule friendly description
|
||||
Description string `json:"description"`
|
||||
|
||||
// Rule destination groups
|
||||
Destinations []GroupMinimum `json:"destinations"`
|
||||
|
||||
// Rules status
|
||||
Disabled bool `json:"disabled"`
|
||||
|
||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
Flow string `json:"flow"`
|
||||
|
||||
// Rule ID
|
||||
Id string `json:"id"`
|
||||
|
||||
// Rule name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Rule source groups
|
||||
Sources []GroupMinimum `json:"sources"`
|
||||
}
|
||||
|
||||
// RuleMinimum defines model for RuleMinimum.
|
||||
type RuleMinimum struct {
|
||||
// Rule friendly description
|
||||
Description string `json:"description"`
|
||||
|
||||
// Rules status
|
||||
Disabled bool `json:"disabled"`
|
||||
|
||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
Flow string `json:"flow"`
|
||||
|
||||
// Rule name identifier
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// RulePatchOperation defines model for RulePatchOperation.
|
||||
type RulePatchOperation struct {
|
||||
// Patch operation type
|
||||
Op RulePatchOperationOp `json:"op"`
|
||||
|
||||
// Rule field to update in form /<field>
|
||||
Path RulePatchOperationPath `json:"path"`
|
||||
|
||||
// Values to be applied
|
||||
Value []string `json:"value"`
|
||||
}
|
||||
|
||||
// Patch operation type
|
||||
type RulePatchOperationOp string
|
||||
|
||||
// Rule field to update in form /<field>
|
||||
type RulePatchOperationPath string
|
||||
|
||||
// SetupKey defines model for SetupKey.
|
||||
type SetupKey struct {
|
||||
// Setup Key expiration date
|
||||
Expires time.Time `json:"expires"`
|
||||
|
||||
// Setup Key ID
|
||||
Id string `json:"id"`
|
||||
|
||||
// Setup Key value
|
||||
Key string `json:"key"`
|
||||
|
||||
// Setup key last usage date
|
||||
LastUsed time.Time `json:"last_used"`
|
||||
|
||||
// Setup key name identifier
|
||||
Name string `json:"name"`
|
||||
|
||||
// Setup key revocation status
|
||||
Revoked bool `json:"revoked"`
|
||||
|
||||
// Setup key status, "valid", "overused","expired" or "revoked"
|
||||
State string `json:"state"`
|
||||
|
||||
// Setup key type, one-off for single time usage and reusable
|
||||
Type string `json:"type"`
|
||||
|
||||
// Usage count of setup key
|
||||
UsedTimes int `json:"used_times"`
|
||||
|
||||
// Setup key validity status
|
||||
Valid bool `json:"valid"`
|
||||
}
|
||||
|
||||
// SetupKeyRequest defines model for SetupKeyRequest.
|
||||
type SetupKeyRequest struct {
|
||||
// Expiration time in seconds
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
|
||||
// Setup Key name
|
||||
Name string `json:"name"`
|
||||
|
||||
// Setup key revocation status
|
||||
Revoked bool `json:"revoked"`
|
||||
|
||||
// Setup key type, one-off for single time usage and reusable
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// User defines model for User.
|
||||
type User struct {
|
||||
// User's email address
|
||||
Email string `json:"email"`
|
||||
|
||||
// User ID
|
||||
Id string `json:"id"`
|
||||
|
||||
// User's name from idp provider
|
||||
Name string `json:"name"`
|
||||
|
||||
// User's Netbird account role
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// PostApiGroupsJSONBody defines parameters for PostApiGroups.
|
||||
type PostApiGroupsJSONBody struct {
|
||||
Name string `json:"name"`
|
||||
Peers *[]string `json:"peers,omitempty"`
|
||||
}
|
||||
|
||||
// PatchApiGroupsIdJSONBody defines parameters for PatchApiGroupsId.
|
||||
type PatchApiGroupsIdJSONBody = []GroupPatchOperation
|
||||
|
||||
// PutApiGroupsIdJSONBody defines parameters for PutApiGroupsId.
|
||||
type PutApiGroupsIdJSONBody struct {
|
||||
Name *string `json:"Name,omitempty"`
|
||||
Peers *[]string `json:"Peers,omitempty"`
|
||||
}
|
||||
|
||||
// PutApiPeersIdJSONBody defines parameters for PutApiPeersId.
|
||||
type PutApiPeersIdJSONBody struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// PostApiRulesJSONBody defines parameters for PostApiRules.
|
||||
type PostApiRulesJSONBody struct {
|
||||
// Rule friendly description
|
||||
Description string `json:"description"`
|
||||
Destinations *[]string `json:"destinations,omitempty"`
|
||||
|
||||
// Rules status
|
||||
Disabled bool `json:"disabled"`
|
||||
|
||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
Flow string `json:"flow"`
|
||||
|
||||
// Rule name identifier
|
||||
Name string `json:"name"`
|
||||
Sources *[]string `json:"sources,omitempty"`
|
||||
}
|
||||
|
||||
// PatchApiRulesIdJSONBody defines parameters for PatchApiRulesId.
|
||||
type PatchApiRulesIdJSONBody = []RulePatchOperation
|
||||
|
||||
// PutApiRulesIdJSONBody defines parameters for PutApiRulesId.
|
||||
type PutApiRulesIdJSONBody struct {
|
||||
// Rule friendly description
|
||||
Description string `json:"description"`
|
||||
Destinations *[]string `json:"destinations,omitempty"`
|
||||
|
||||
// Rules status
|
||||
Disabled bool `json:"disabled"`
|
||||
|
||||
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
Flow string `json:"flow"`
|
||||
|
||||
// Rule name identifier
|
||||
Name string `json:"name"`
|
||||
Sources *[]string `json:"sources,omitempty"`
|
||||
}
|
||||
|
||||
// PostApiSetupKeysJSONBody defines parameters for PostApiSetupKeys.
|
||||
type PostApiSetupKeysJSONBody = SetupKeyRequest
|
||||
|
||||
// PutApiSetupKeysIdJSONBody defines parameters for PutApiSetupKeysId.
|
||||
type PutApiSetupKeysIdJSONBody = SetupKeyRequest
|
||||
|
||||
// PostApiGroupsJSONRequestBody defines body for PostApiGroups for application/json ContentType.
|
||||
type PostApiGroupsJSONRequestBody PostApiGroupsJSONBody
|
||||
|
||||
// PatchApiGroupsIdJSONRequestBody defines body for PatchApiGroupsId for application/json ContentType.
|
||||
type PatchApiGroupsIdJSONRequestBody = PatchApiGroupsIdJSONBody
|
||||
|
||||
// PutApiGroupsIdJSONRequestBody defines body for PutApiGroupsId for application/json ContentType.
|
||||
type PutApiGroupsIdJSONRequestBody PutApiGroupsIdJSONBody
|
||||
|
||||
// PutApiPeersIdJSONRequestBody defines body for PutApiPeersId for application/json ContentType.
|
||||
type PutApiPeersIdJSONRequestBody PutApiPeersIdJSONBody
|
||||
|
||||
// PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType.
|
||||
type PostApiRulesJSONRequestBody PostApiRulesJSONBody
|
||||
|
||||
// PatchApiRulesIdJSONRequestBody defines body for PatchApiRulesId for application/json ContentType.
|
||||
type PatchApiRulesIdJSONRequestBody = PatchApiRulesIdJSONBody
|
||||
|
||||
// PutApiRulesIdJSONRequestBody defines body for PutApiRulesId for application/json ContentType.
|
||||
type PutApiRulesIdJSONRequestBody PutApiRulesIdJSONBody
|
||||
|
||||
// PostApiSetupKeysJSONRequestBody defines body for PostApiSetupKeys for application/json ContentType.
|
||||
type PostApiSetupKeysJSONRequestBody = PostApiSetupKeysJSONBody
|
||||
|
||||
// PutApiSetupKeysIdJSONRequestBody defines body for PutApiSetupKeysId for application/json ContentType.
|
||||
type PutApiSetupKeysIdJSONRequestBody = PutApiSetupKeysIdJSONBody
|
@ -3,6 +3,9 @@ package handler
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net/http"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
@ -13,26 +16,6 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GroupResponse is a response sent to the client
|
||||
type GroupResponse struct {
|
||||
ID string
|
||||
Name string
|
||||
Peers []GroupPeerResponse `json:",omitempty"`
|
||||
}
|
||||
|
||||
// GroupPeerResponse is a response sent to the client
|
||||
type GroupPeerResponse struct {
|
||||
Key string
|
||||
Name string
|
||||
}
|
||||
|
||||
// GroupRequest to create or update group
|
||||
type GroupRequest struct {
|
||||
ID string
|
||||
Name string
|
||||
Peers []string
|
||||
}
|
||||
|
||||
// Groups is a handler that returns groups of the account
|
||||
type Groups struct {
|
||||
jwtExtractor jwtclaims.ClaimsExtractor
|
||||
@ -50,14 +33,14 @@ func NewGroups(accountManager server.AccountManager, authAudience string) *Group
|
||||
|
||||
// GetAllGroupsHandler list for the account
|
||||
func (h *Groups) GetAllGroupsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.getGroupAccount(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var groups []*GroupResponse
|
||||
var groups []*api.Group
|
||||
for _, g := range account.Groups {
|
||||
groups = append(groups, toGroupResponse(account, g))
|
||||
}
|
||||
@ -65,31 +48,60 @@ func (h *Groups) GetAllGroupsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSONObject(w, groups)
|
||||
}
|
||||
|
||||
func (h *Groups) CreateOrUpdateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.getGroupAccount(r)
|
||||
// UpdateGroupHandler handles update to a group identified by a given ID
|
||||
func (h *Groups) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var req GroupRequest
|
||||
vars := mux.Vars(r)
|
||||
groupID, ok := vars["id"]
|
||||
if !ok {
|
||||
http.Error(w, "group ID field is missing", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(groupID) == 0 {
|
||||
http.Error(w, "group ID can't be empty", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
_, ok = account.Groups[groupID]
|
||||
if !ok {
|
||||
http.Error(w, fmt.Sprintf("couldn't find group with ID %s", groupID), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
allGroup, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if allGroup.ID == groupID {
|
||||
http.Error(w, "updating group ALL is not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PutApiGroupsIdJSONRequestBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
req.ID = xid.New().String()
|
||||
if *req.Name == "" {
|
||||
http.Error(w, "group name shouldn't be empty", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
group := server.Group{
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
Peers: req.Peers,
|
||||
ID: groupID,
|
||||
Name: *req.Name,
|
||||
Peers: peerIPsToKeys(account, req.Peers),
|
||||
}
|
||||
|
||||
if err := h.accountManager.SaveGroup(account.Id, &group); err != nil {
|
||||
log.Errorf("failed updating group %s under account %s %v", req.ID, account.Id, err)
|
||||
log.Errorf("failed updating group %s under account %s %v", groupID, account.Id, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@ -97,22 +109,183 @@ func (h *Groups) CreateOrUpdateGroupHandler(w http.ResponseWriter, r *http.Reque
|
||||
writeJSONObject(w, toGroupResponse(account, &group))
|
||||
}
|
||||
|
||||
// PatchGroupHandler handles patch updates to a group identified by a given ID
|
||||
func (h *Groups) PatchGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
groupID := vars["id"]
|
||||
if len(groupID) == 0 {
|
||||
http.Error(w, "invalid group Id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, ok := account.Groups[groupID]
|
||||
if !ok {
|
||||
http.Error(w, fmt.Sprintf("couldn't find group id %s", groupID), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
allGroup, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if allGroup.ID == groupID {
|
||||
http.Error(w, "updating group ALL is not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PatchApiGroupsIdJSONRequestBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if len(req) == 0 {
|
||||
http.Error(w, "no patch instruction received", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var operations []server.GroupUpdateOperation
|
||||
|
||||
for _, patch := range req {
|
||||
switch patch.Path {
|
||||
case api.GroupPatchOperationPathName:
|
||||
if patch.Op != api.GroupPatchOperationOpReplace {
|
||||
http.Error(w, fmt.Sprintf("Name field only accepts replace operation, got %s", patch.Op),
|
||||
http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if len(patch.Value) == 0 || patch.Value[0] == "" {
|
||||
http.Error(w, "Group name shouldn't be empty", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
operations = append(operations, server.GroupUpdateOperation{
|
||||
Type: server.UpdateGroupName,
|
||||
Values: patch.Value,
|
||||
})
|
||||
case api.GroupPatchOperationPathPeers:
|
||||
switch patch.Op {
|
||||
case api.GroupPatchOperationOpReplace:
|
||||
peerKeys := peerIPsToKeys(account, &patch.Value)
|
||||
operations = append(operations, server.GroupUpdateOperation{
|
||||
Type: server.UpdateGroupPeers,
|
||||
Values: peerKeys,
|
||||
})
|
||||
case api.GroupPatchOperationOpRemove:
|
||||
peerKeys := peerIPsToKeys(account, &patch.Value)
|
||||
operations = append(operations, server.GroupUpdateOperation{
|
||||
Type: server.RemovePeersFromGroup,
|
||||
Values: peerKeys,
|
||||
})
|
||||
case api.GroupPatchOperationOpAdd:
|
||||
peerKeys := peerIPsToKeys(account, &patch.Value)
|
||||
operations = append(operations, server.GroupUpdateOperation{
|
||||
Type: server.InsertPeersToGroup,
|
||||
Values: peerKeys,
|
||||
})
|
||||
default:
|
||||
http.Error(w, "invalid operation, \"%s\", for Peers field", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
default:
|
||||
http.Error(w, "invalid patch path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
group, err := h.accountManager.UpdateGroup(account.Id, groupID, operations)
|
||||
|
||||
if err != nil {
|
||||
errStatus, ok := status.FromError(err)
|
||||
if ok && errStatus.Code() == codes.Internal {
|
||||
http.Error(w, errStatus.String(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if ok && errStatus.Code() == codes.NotFound {
|
||||
http.Error(w, errStatus.String(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
log.Errorf("failed updating group %s under account %s %v", groupID, account.Id, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSONObject(w, toGroupResponse(account, group))
|
||||
}
|
||||
|
||||
// CreateGroupHandler handles group creation request
|
||||
func (h *Groups) CreateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PostApiGroupsJSONRequestBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
http.Error(w, "Group name shouldn't be empty", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
group := server.Group{
|
||||
ID: xid.New().String(),
|
||||
Name: req.Name,
|
||||
Peers: peerIPsToKeys(account, req.Peers),
|
||||
}
|
||||
|
||||
if err := h.accountManager.SaveGroup(account.Id, &group); err != nil {
|
||||
log.Errorf("failed creating group \"%s\" under account %s %v", req.Name, account.Id, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSONObject(w, toGroupResponse(account, &group))
|
||||
}
|
||||
|
||||
// DeleteGroupHandler handles group deletion request
|
||||
func (h *Groups) DeleteGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.getGroupAccount(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
aID := account.Id
|
||||
|
||||
gID := mux.Vars(r)["id"]
|
||||
if len(gID) == 0 {
|
||||
groupID := mux.Vars(r)["id"]
|
||||
if len(groupID) == 0 {
|
||||
http.Error(w, "invalid group ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.accountManager.DeleteGroup(aID, gID); err != nil {
|
||||
log.Errorf("failed delete group %s under account %s %v", gID, aID, err)
|
||||
allGroup, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if allGroup.ID == groupID {
|
||||
http.Error(w, "deleting group ALL is not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.accountManager.DeleteGroup(aID, groupID); err != nil {
|
||||
log.Errorf("failed delete group %s under account %s %v", groupID, aID, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@ -120,8 +293,9 @@ func (h *Groups) DeleteGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSONObject(w, "")
|
||||
}
|
||||
|
||||
// GetGroupHandler returns a group
|
||||
func (h *Groups) GetGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.getGroupAccount(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
@ -147,39 +321,51 @@ func (h *Groups) GetGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Groups) getGroupAccount(r *http.Request) (*server.Account, error) {
|
||||
jwtClaims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
|
||||
account, err := h.accountManager.GetAccountWithAuthorizationClaims(jwtClaims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
||||
func peerIPsToKeys(account *server.Account, peerIPs *[]string) []string {
|
||||
var mappedPeerKeys []string
|
||||
if peerIPs == nil {
|
||||
return mappedPeerKeys
|
||||
}
|
||||
|
||||
return account, nil
|
||||
peersChecked := make(map[string]struct{})
|
||||
|
||||
for _, requestPeersIP := range *peerIPs {
|
||||
_, ok := peersChecked[requestPeersIP]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
peersChecked[requestPeersIP] = struct{}{}
|
||||
for _, accountPeer := range account.Peers {
|
||||
if accountPeer.IP.String() == requestPeersIP {
|
||||
mappedPeerKeys = append(mappedPeerKeys, accountPeer.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return mappedPeerKeys
|
||||
}
|
||||
|
||||
func toGroupResponse(account *server.Account, group *server.Group) *GroupResponse {
|
||||
cache := make(map[string]GroupPeerResponse)
|
||||
gr := GroupResponse{
|
||||
ID: group.ID,
|
||||
Name: group.Name,
|
||||
func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
|
||||
cache := make(map[string]api.PeerMinimum)
|
||||
gr := api.Group{
|
||||
Id: group.ID,
|
||||
Name: group.Name,
|
||||
PeersCount: len(group.Peers),
|
||||
}
|
||||
|
||||
for _, pid := range group.Peers {
|
||||
peerResp, ok := cache[pid]
|
||||
_, ok := cache[pid]
|
||||
if !ok {
|
||||
peer, ok := account.Peers[pid]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
peerResp = GroupPeerResponse{
|
||||
Key: peer.Key,
|
||||
peerResp := api.PeerMinimum{
|
||||
Id: peer.IP.String(),
|
||||
Name: peer.Name,
|
||||
}
|
||||
cache[pid] = peerResp
|
||||
gr.Peers = append(gr.Peers, peerResp)
|
||||
}
|
||||
gr.Peers = append(gr.Peers, peerResp)
|
||||
}
|
||||
|
||||
return &gr
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@ -18,6 +20,11 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||
)
|
||||
|
||||
var TestPeers = map[string]*server.Peer{
|
||||
"A": &server.Peer{Key: "A", IP: net.ParseIP("100.100.100.100")},
|
||||
"B": &server.Peer{Key: "B", IP: net.ParseIP("200.200.200.200")},
|
||||
}
|
||||
|
||||
func initGroupTestData(groups ...*server.Group) *Groups {
|
||||
return &Groups{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
@ -36,10 +43,38 @@ func initGroupTestData(groups ...*server.Group) *Groups {
|
||||
Name: "Group",
|
||||
}, nil
|
||||
},
|
||||
UpdateGroupFunc: func(_ string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error) {
|
||||
var group server.Group
|
||||
group.ID = groupID
|
||||
for _, operation := range operations {
|
||||
switch operation.Type {
|
||||
case server.UpdateGroupName:
|
||||
group.Name = operation.Values[0]
|
||||
case server.UpdateGroupPeers, server.InsertPeersToGroup:
|
||||
group.Peers = operation.Values
|
||||
case server.RemovePeersFromGroup:
|
||||
default:
|
||||
return nil, fmt.Errorf("no operation")
|
||||
}
|
||||
}
|
||||
return &group, nil
|
||||
},
|
||||
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
||||
for _, peer := range TestPeers {
|
||||
if peer.IP.String() == peerIP {
|
||||
return peer, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("peer not found")
|
||||
},
|
||||
GetAccountWithAuthorizationClaimsFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
||||
return &server.Account{
|
||||
Id: claims.AccountId,
|
||||
Domain: "hotmail.com",
|
||||
Peers: TestPeers,
|
||||
Groups: map[string]*server.Group{
|
||||
"id-existed": &server.Group{ID: "id-existed", Peers: []string{"A", "B"}},
|
||||
"id-all": &server.Group{ID: "id-all", Name: "All"}},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
@ -125,41 +160,114 @@ func TestGetGroup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveGroup(t *testing.T) {
|
||||
func TestWriteGroup(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
expectedStatus int
|
||||
expectedBody bool
|
||||
expectedGroup *server.Group
|
||||
expectedGroup *api.Group
|
||||
requestType string
|
||||
requestPath string
|
||||
requestBody io.Reader
|
||||
}{
|
||||
{
|
||||
name: "SaveGroup POST OK",
|
||||
name: "Write Group POST OK",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/groups",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":"Default POSTed Group"}`)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedGroup: &server.Group{
|
||||
ID: "id-was-set",
|
||||
expectedGroup: &api.Group{
|
||||
Id: "id-was-set",
|
||||
Name: "Default POSTed Group",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SaveGroup PUT OK",
|
||||
requestType: http.MethodPut,
|
||||
name: "Write Group POST Invalid Name",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/groups",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"ID":"id-existed","Name":"Default POSTed Group"}`)),
|
||||
[]byte(`{"name":""}`)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "Write Group PUT OK",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/groups/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":"Default POSTed Group"}`)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedGroup: &server.Group{
|
||||
ID: "id-existed",
|
||||
expectedGroup: &api.Group{
|
||||
Id: "id-existed",
|
||||
Name: "Default POSTed Group",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Write Group PUT Invalid Name",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/groups/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":""}`)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "Write Group PUT All Group Name",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/groups/id-all",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":"super"}`)),
|
||||
expectedStatus: http.StatusMethodNotAllowed,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "Write Group PATCH Name OK",
|
||||
requestType: http.MethodPatch,
|
||||
requestPath: "/api/groups/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`[{"op":"replace","path":"name","value":["Default POSTed Group"]}]`)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedGroup: &api.Group{
|
||||
Id: "id-existed",
|
||||
Name: "Default POSTed Group",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Write Group PATCH Invalid Name OP",
|
||||
requestType: http.MethodPatch,
|
||||
requestPath: "/api/groups/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`[{"op":"insert","path":"name","value":[""]}]`)),
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "Write Group PATCH Invalid Name",
|
||||
requestType: http.MethodPatch,
|
||||
requestPath: "/api/groups/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`[{"op":"replace","path":"name","value":[]}]`)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "Write Group PATCH Peers OK",
|
||||
requestType: http.MethodPatch,
|
||||
requestPath: "/api/groups/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`[{"op":"replace","path":"peers","value":["100.100.100.100","200.200.200.200"]}]`)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedGroup: &api.Group{
|
||||
Id: "id-existed",
|
||||
PeersCount: 2,
|
||||
Peers: []api.PeerMinimum{
|
||||
{Id: "100.100.100.100"},
|
||||
{Id: "200.200.200.200"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
p := initGroupTestData()
|
||||
@ -170,7 +278,9 @@ func TestSaveGroup(t *testing.T) {
|
||||
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/groups", p.CreateOrUpdateGroupHandler).Methods("PUT", "POST")
|
||||
router.HandleFunc("/api/groups", p.CreateGroupHandler).Methods("POST")
|
||||
router.HandleFunc("/api/groups/{id}", p.UpdateGroupHandler).Methods("PUT")
|
||||
router.HandleFunc("/api/groups/{id}", p.PatchGroupHandler).Methods("PATCH")
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
res := recorder.Result()
|
||||
@ -191,11 +301,10 @@ func TestSaveGroup(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
got := &server.Group{}
|
||||
got := &api.Group{}
|
||||
if err = json.Unmarshal(content, &got); err != nil {
|
||||
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, got, tc.expectedGroup)
|
||||
})
|
||||
}
|
||||
|
@ -3,13 +3,12 @@ package handler
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//Peers is a handler that returns peers of the account
|
||||
@ -19,21 +18,6 @@ type Peers struct {
|
||||
jwtExtractor jwtclaims.ClaimsExtractor
|
||||
}
|
||||
|
||||
//PeerResponse is a response sent to the client
|
||||
type PeerResponse struct {
|
||||
Name string
|
||||
IP string
|
||||
Connected bool
|
||||
LastSeen time.Time
|
||||
OS string
|
||||
Version string
|
||||
}
|
||||
|
||||
//PeerRequest is a request sent by the client
|
||||
type PeerRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewPeers(accountManager server.AccountManager, authAudience string) *Peers {
|
||||
return &Peers{
|
||||
accountManager: accountManager,
|
||||
@ -42,21 +26,21 @@ func NewPeers(accountManager server.AccountManager, authAudience string) *Peers
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Peers) updatePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||
req := &PeerRequest{}
|
||||
func (h *Peers) updatePeer(account *server.Account, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||
req := &api.PutApiPeersIdJSONBody{}
|
||||
peerIp := peer.IP
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
peer, err = h.accountManager.RenamePeer(accountId, peer.Key, req.Name)
|
||||
peer, err = h.accountManager.RenamePeer(account.Id, peer.Key, req.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failed updating peer %s under account %s %v", peerIp, accountId, err)
|
||||
log.Errorf("failed updating peer %s under account %s %v", peerIp, account.Id, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSONObject(w, toPeerResponse(peer))
|
||||
writeJSONObject(w, toPeerResponse(peer, account))
|
||||
}
|
||||
|
||||
func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||
@ -69,19 +53,8 @@ func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseW
|
||||
writeJSONObject(w, "")
|
||||
}
|
||||
|
||||
func (h *Peers) getPeerAccount(r *http.Request) (*server.Account, error) {
|
||||
jwtClaims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
|
||||
account, err := h.accountManager.GetAccountWithAuthorizationClaims(jwtClaims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.getPeerAccount(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
@ -105,10 +78,10 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||
h.deletePeer(account.Id, peer, w, r)
|
||||
return
|
||||
case http.MethodPut:
|
||||
h.updatePeer(account.Id, peer, w, r)
|
||||
h.updatePeer(account, peer, w, r)
|
||||
return
|
||||
case http.MethodGet:
|
||||
writeJSONObject(w, toPeerResponse(peer))
|
||||
writeJSONObject(w, toPeerResponse(peer, account))
|
||||
return
|
||||
|
||||
default:
|
||||
@ -120,16 +93,16 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
account, err := h.getPeerAccount(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
respBody := []*PeerResponse{}
|
||||
respBody := []*api.Peer{}
|
||||
for _, peer := range account.Peers {
|
||||
respBody = append(respBody, toPeerResponse(peer))
|
||||
respBody = append(respBody, toPeerResponse(peer, account))
|
||||
}
|
||||
writeJSONObject(w, respBody)
|
||||
return
|
||||
@ -138,13 +111,35 @@ func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func toPeerResponse(peer *server.Peer) *PeerResponse {
|
||||
return &PeerResponse{
|
||||
func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer {
|
||||
var groupsInfo []api.GroupMinimum
|
||||
groupsChecked := make(map[string]struct{})
|
||||
for _, group := range account.Groups {
|
||||
_, ok := groupsChecked[group.ID]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
groupsChecked[group.ID] = struct{}{}
|
||||
for _, pk := range group.Peers {
|
||||
if pk == peer.Key {
|
||||
info := api.GroupMinimum{
|
||||
Id: group.ID,
|
||||
Name: group.Name,
|
||||
PeersCount: len(group.Peers),
|
||||
}
|
||||
groupsInfo = append(groupsInfo, info)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return &api.Peer{
|
||||
Id: peer.IP.String(),
|
||||
Name: peer.Name,
|
||||
IP: peer.IP.String(),
|
||||
Ip: peer.IP.String(),
|
||||
Connected: peer.Status.Connected,
|
||||
LastSeen: peer.Status.LastSeen,
|
||||
OS: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core),
|
||||
Os: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core),
|
||||
Version: peer.Meta.WtVersion,
|
||||
Groups: groupsInfo,
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -98,7 +99,7 @@ func TestGetPeers(t *testing.T) {
|
||||
t.Fatalf("I don't know what I expected; %v", err)
|
||||
}
|
||||
|
||||
respBody := []*PeerResponse{}
|
||||
respBody := []*api.Peer{}
|
||||
err = json.Unmarshal(content, &respBody)
|
||||
if err != nil {
|
||||
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||
@ -107,8 +108,8 @@ func TestGetPeers(t *testing.T) {
|
||||
got := respBody[0]
|
||||
assert.Equal(t, got.Name, peer.Name)
|
||||
assert.Equal(t, got.Version, peer.Meta.WtVersion)
|
||||
assert.Equal(t, got.IP, peer.IP.String())
|
||||
assert.Equal(t, got.OS, "OS core")
|
||||
assert.Equal(t, got.Ip, peer.IP.String())
|
||||
assert.Equal(t, got.Os, "OS core")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -3,43 +3,17 @@ package handler
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
"github.com/rs/xid"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const FlowBidirectString = "bidirect"
|
||||
|
||||
// RuleResponse is a response sent to the client
|
||||
type RuleResponse struct {
|
||||
ID string
|
||||
Name string
|
||||
Source []RuleGroupResponse
|
||||
Destination []RuleGroupResponse
|
||||
Flow string
|
||||
}
|
||||
|
||||
// RuleGroupResponse is a response sent to the client
|
||||
type RuleGroupResponse struct {
|
||||
ID string
|
||||
Name string
|
||||
PeersCount int
|
||||
}
|
||||
|
||||
// RuleRequest to create or update rule
|
||||
type RuleRequest struct {
|
||||
ID string
|
||||
Name string
|
||||
Source []string
|
||||
Destination []string
|
||||
Flow string
|
||||
}
|
||||
|
||||
// Rules is a handler that returns rules of the account
|
||||
type Rules struct {
|
||||
jwtExtractor jwtclaims.ClaimsExtractor
|
||||
@ -57,14 +31,14 @@ func NewRules(accountManager server.AccountManager, authAudience string) *Rules
|
||||
|
||||
// GetAllRulesHandler list for the account
|
||||
func (h *Rules) GetAllRulesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.getRuleAccount(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rules := []*RuleResponse{}
|
||||
rules := []*api.Rule{}
|
||||
for _, r := range account.Rules {
|
||||
rules = append(rules, toRuleResponse(account, r))
|
||||
}
|
||||
@ -72,32 +46,59 @@ func (h *Rules) GetAllRulesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSONObject(w, rules)
|
||||
}
|
||||
|
||||
func (h *Rules) CreateOrUpdateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.getRuleAccount(r)
|
||||
// UpdateRuleHandler handles update to a rule identified by a given ID
|
||||
func (h *Rules) UpdateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var req RuleRequest
|
||||
vars := mux.Vars(r)
|
||||
ruleID := vars["id"]
|
||||
if len(ruleID) == 0 {
|
||||
http.Error(w, "invalid rule Id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, ok := account.Rules[ruleID]
|
||||
if !ok {
|
||||
http.Error(w, fmt.Sprintf("couldn't find rule id %s", ruleID), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PutApiRulesIdJSONRequestBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
req.ID = xid.New().String()
|
||||
if req.Name == "" {
|
||||
http.Error(w, "Rule name shouldn't be empty", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
var reqSources []string
|
||||
if req.Sources != nil {
|
||||
reqSources = *req.Sources
|
||||
}
|
||||
|
||||
var reqDestinations []string
|
||||
if req.Destinations != nil {
|
||||
reqDestinations = *req.Destinations
|
||||
}
|
||||
|
||||
rule := server.Rule{
|
||||
ID: req.ID,
|
||||
ID: ruleID,
|
||||
Name: req.Name,
|
||||
Source: req.Source,
|
||||
Destination: req.Destination,
|
||||
Source: reqSources,
|
||||
Destination: reqDestinations,
|
||||
Disabled: req.Disabled,
|
||||
Description: req.Description,
|
||||
}
|
||||
|
||||
switch req.Flow {
|
||||
case FlowBidirectString:
|
||||
case server.TrafficFlowBidirectString:
|
||||
rule.Flow = server.TrafficFlowBidirect
|
||||
default:
|
||||
http.Error(w, "unknown flow type", http.StatusBadRequest)
|
||||
@ -105,16 +106,233 @@ func (h *Rules) CreateOrUpdateRuleHandler(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
if err := h.accountManager.SaveRule(account.Id, &rule); err != nil {
|
||||
log.Errorf("failed updating rule %s under account %s %v", req.ID, account.Id, err)
|
||||
log.Errorf("failed updating rule \"%s\" under account %s %v", ruleID, account.Id, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSONObject(w, &req)
|
||||
resp := toRuleResponse(account, &rule)
|
||||
|
||||
writeJSONObject(w, &resp)
|
||||
}
|
||||
|
||||
// PatchRuleHandler handles patch updates to a rule identified by a given ID
|
||||
func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
ruleID := vars["id"]
|
||||
if len(ruleID) == 0 {
|
||||
http.Error(w, "invalid rule Id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, ok := account.Rules[ruleID]
|
||||
if !ok {
|
||||
http.Error(w, fmt.Sprintf("couldn't find rule id %s", ruleID), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PatchApiRulesIdJSONRequestBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if len(req) == 0 {
|
||||
http.Error(w, "no patch instruction received", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var operations []server.RuleUpdateOperation
|
||||
|
||||
for _, patch := range req {
|
||||
switch patch.Path {
|
||||
case api.RulePatchOperationPathName:
|
||||
if patch.Op != api.RulePatchOperationOpReplace {
|
||||
http.Error(w, fmt.Sprintf("Name field only accepts replace operation, got %s", patch.Op),
|
||||
http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(patch.Value) == 0 || patch.Value[0] == "" {
|
||||
http.Error(w, "Rule name shouldn't be empty", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
operations = append(operations, server.RuleUpdateOperation{
|
||||
Type: server.UpdateRuleName,
|
||||
Values: patch.Value,
|
||||
})
|
||||
case api.RulePatchOperationPathDescription:
|
||||
if patch.Op != api.RulePatchOperationOpReplace {
|
||||
http.Error(w, fmt.Sprintf("Description field only accepts replace operation, got %s", patch.Op),
|
||||
http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
operations = append(operations, server.RuleUpdateOperation{
|
||||
Type: server.UpdateRuleDescription,
|
||||
Values: patch.Value,
|
||||
})
|
||||
case api.RulePatchOperationPathFlow:
|
||||
if patch.Op != api.RulePatchOperationOpReplace {
|
||||
http.Error(w, fmt.Sprintf("Flow field only accepts replace operation, got %s", patch.Op),
|
||||
http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
operations = append(operations, server.RuleUpdateOperation{
|
||||
Type: server.UpdateRuleFlow,
|
||||
Values: patch.Value,
|
||||
})
|
||||
case api.RulePatchOperationPathDisabled:
|
||||
if patch.Op != api.RulePatchOperationOpReplace {
|
||||
http.Error(w, fmt.Sprintf("Disabled field only accepts replace operation, got %s", patch.Op),
|
||||
http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
operations = append(operations, server.RuleUpdateOperation{
|
||||
Type: server.UpdateRuleStatus,
|
||||
Values: patch.Value,
|
||||
})
|
||||
case api.RulePatchOperationPathSources:
|
||||
switch patch.Op {
|
||||
case api.RulePatchOperationOpReplace:
|
||||
operations = append(operations, server.RuleUpdateOperation{
|
||||
Type: server.UpdateSourceGroups,
|
||||
Values: patch.Value,
|
||||
})
|
||||
case api.RulePatchOperationOpRemove:
|
||||
operations = append(operations, server.RuleUpdateOperation{
|
||||
Type: server.RemoveGroupsFromSource,
|
||||
Values: patch.Value,
|
||||
})
|
||||
case api.RulePatchOperationOpAdd:
|
||||
operations = append(operations, server.RuleUpdateOperation{
|
||||
Type: server.InsertGroupsToSource,
|
||||
Values: patch.Value,
|
||||
})
|
||||
default:
|
||||
http.Error(w, "invalid operation, \"%s\", for Source field", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
case api.RulePatchOperationPathDestinations:
|
||||
switch patch.Op {
|
||||
case api.RulePatchOperationOpReplace:
|
||||
operations = append(operations, server.RuleUpdateOperation{
|
||||
Type: server.UpdateDestinationGroups,
|
||||
Values: patch.Value,
|
||||
})
|
||||
case api.RulePatchOperationOpRemove:
|
||||
operations = append(operations, server.RuleUpdateOperation{
|
||||
Type: server.RemoveGroupsFromDestination,
|
||||
Values: patch.Value,
|
||||
})
|
||||
case api.RulePatchOperationOpAdd:
|
||||
operations = append(operations, server.RuleUpdateOperation{
|
||||
Type: server.InsertGroupsToDestination,
|
||||
Values: patch.Value,
|
||||
})
|
||||
default:
|
||||
http.Error(w, "invalid operation, \"%s\", for Destination field", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
default:
|
||||
http.Error(w, "invalid patch path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rule, err := h.accountManager.UpdateRule(account.Id, ruleID, operations)
|
||||
|
||||
if err != nil {
|
||||
errStatus, ok := status.FromError(err)
|
||||
if ok && errStatus.Code() == codes.Internal {
|
||||
http.Error(w, errStatus.String(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if ok && errStatus.Code() == codes.NotFound {
|
||||
http.Error(w, errStatus.String(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if ok && errStatus.Code() == codes.InvalidArgument {
|
||||
http.Error(w, errStatus.String(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Errorf("failed updating rule %s under account %s %v", ruleID, account.Id, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := toRuleResponse(account, rule)
|
||||
|
||||
writeJSONObject(w, &resp)
|
||||
}
|
||||
|
||||
// CreateRuleHandler handles rule creation request
|
||||
func (h *Rules) CreateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PostApiRulesJSONRequestBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
http.Error(w, "Rule name shouldn't be empty", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
var reqSources []string
|
||||
if req.Sources != nil {
|
||||
reqSources = *req.Sources
|
||||
}
|
||||
|
||||
var reqDestinations []string
|
||||
if req.Destinations != nil {
|
||||
reqDestinations = *req.Destinations
|
||||
}
|
||||
|
||||
rule := server.Rule{
|
||||
ID: xid.New().String(),
|
||||
Name: req.Name,
|
||||
Source: reqSources,
|
||||
Destination: reqDestinations,
|
||||
Disabled: req.Disabled,
|
||||
Description: req.Description,
|
||||
}
|
||||
|
||||
switch req.Flow {
|
||||
case server.TrafficFlowBidirectString:
|
||||
rule.Flow = server.TrafficFlowBidirect
|
||||
default:
|
||||
http.Error(w, "unknown flow type", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.accountManager.SaveRule(account.Id, &rule); err != nil {
|
||||
log.Errorf("failed creating rule \"%s\" under account %s %v", req.Name, account.Id, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := toRuleResponse(account, &rule)
|
||||
|
||||
writeJSONObject(w, &resp)
|
||||
}
|
||||
|
||||
// DeleteRuleHandler handles rule deletion request
|
||||
func (h *Rules) DeleteRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.getRuleAccount(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
@ -136,8 +354,9 @@ func (h *Rules) DeleteRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSONObject(w, "")
|
||||
}
|
||||
|
||||
// GetRuleHandler handles a group Get request identified by ID
|
||||
func (h *Rules) GetRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.getRuleAccount(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
@ -163,47 +382,54 @@ func (h *Rules) GetRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Rules) getRuleAccount(r *http.Request) (*server.Account, error) {
|
||||
jwtClaims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
|
||||
account, err := h.accountManager.GetAccountWithAuthorizationClaims(jwtClaims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func toRuleResponse(account *server.Account, rule *server.Rule) *RuleResponse {
|
||||
gr := RuleResponse{
|
||||
ID: rule.ID,
|
||||
Name: rule.Name,
|
||||
func toRuleResponse(account *server.Account, rule *server.Rule) *api.Rule {
|
||||
cache := make(map[string]api.GroupMinimum)
|
||||
gr := api.Rule{
|
||||
Id: rule.ID,
|
||||
Name: rule.Name,
|
||||
Description: rule.Description,
|
||||
Disabled: rule.Disabled,
|
||||
}
|
||||
|
||||
switch rule.Flow {
|
||||
case server.TrafficFlowBidirect:
|
||||
gr.Flow = FlowBidirectString
|
||||
gr.Flow = server.TrafficFlowBidirectString
|
||||
default:
|
||||
gr.Flow = "unknown"
|
||||
}
|
||||
|
||||
for _, gid := range rule.Source {
|
||||
_, ok := cache[gid]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if group, ok := account.Groups[gid]; ok {
|
||||
gr.Source = append(gr.Source, RuleGroupResponse{
|
||||
ID: group.ID,
|
||||
minimum := api.GroupMinimum{
|
||||
Id: group.ID,
|
||||
Name: group.Name,
|
||||
PeersCount: len(group.Peers),
|
||||
})
|
||||
}
|
||||
|
||||
gr.Sources = append(gr.Sources, minimum)
|
||||
cache[gid] = minimum
|
||||
}
|
||||
}
|
||||
|
||||
for _, gid := range rule.Destination {
|
||||
cachedMinimum, ok := cache[gid]
|
||||
if ok {
|
||||
gr.Destinations = append(gr.Destinations, cachedMinimum)
|
||||
continue
|
||||
}
|
||||
if group, ok := account.Groups[gid]; ok {
|
||||
gr.Destination = append(gr.Destination, RuleGroupResponse{
|
||||
ID: group.ID,
|
||||
minimum := api.GroupMinimum{
|
||||
Id: group.ID,
|
||||
Name: group.Name,
|
||||
PeersCount: len(group.Peers),
|
||||
})
|
||||
}
|
||||
gr.Destinations = append(gr.Destinations, minimum)
|
||||
cache[gid] = minimum
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -39,10 +40,41 @@ func initRulesTestData(rules ...*server.Rule) *Rules {
|
||||
Flow: server.TrafficFlowBidirect,
|
||||
}, nil
|
||||
},
|
||||
UpdateRuleFunc: func(_ string, ruleID string, operations []server.RuleUpdateOperation) (*server.Rule, error) {
|
||||
var rule server.Rule
|
||||
rule.ID = ruleID
|
||||
for _, operation := range operations {
|
||||
switch operation.Type {
|
||||
case server.UpdateRuleName:
|
||||
rule.Name = operation.Values[0]
|
||||
case server.UpdateRuleDescription:
|
||||
rule.Description = operation.Values[0]
|
||||
case server.UpdateRuleFlow:
|
||||
if server.TrafficFlowBidirectString == operation.Values[0] {
|
||||
rule.Flow = server.TrafficFlowBidirect
|
||||
} else {
|
||||
rule.Flow = 100
|
||||
}
|
||||
case server.UpdateSourceGroups, server.InsertGroupsToSource:
|
||||
rule.Source = operation.Values
|
||||
case server.UpdateDestinationGroups, server.InsertGroupsToDestination:
|
||||
rule.Destination = operation.Values
|
||||
case server.RemoveGroupsFromSource, server.RemoveGroupsFromDestination:
|
||||
default:
|
||||
return nil, fmt.Errorf("no operation")
|
||||
}
|
||||
}
|
||||
return &rule, nil
|
||||
},
|
||||
GetAccountWithAuthorizationClaimsFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
||||
return &server.Account{
|
||||
Id: claims.AccountId,
|
||||
Domain: "hotmail.com",
|
||||
Rules: map[string]*server.Rule{"id-existed": &server.Rule{ID: "id-existed"}},
|
||||
Groups: map[string]*server.Group{
|
||||
"F": &server.Group{ID: "F"},
|
||||
"G": &server.Group{ID: "G"},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
@ -117,52 +149,118 @@ func TestRulesGetRule(t *testing.T) {
|
||||
t.Fatalf("I don't know what I expected; %v", err)
|
||||
}
|
||||
|
||||
var got RuleResponse
|
||||
var got api.Rule
|
||||
if err = json.Unmarshal(content, &got); err != nil {
|
||||
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, got.ID, rule.ID)
|
||||
assert.Equal(t, got.Id, rule.ID)
|
||||
assert.Equal(t, got.Name, rule.Name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRulesSaveRule(t *testing.T) {
|
||||
func TestRulesWriteRule(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
expectedStatus int
|
||||
expectedBody bool
|
||||
expectedRule *server.Rule
|
||||
expectedRule *api.Rule
|
||||
requestType string
|
||||
requestPath string
|
||||
requestBody io.Reader
|
||||
}{
|
||||
{
|
||||
name: "SaveRule POST OK",
|
||||
name: "WriteRule POST OK",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/rules",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":"Default POSTed Rule","Flow":"bidirect"}`)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedRule: &server.Rule{
|
||||
ID: "id-was-set",
|
||||
expectedRule: &api.Rule{
|
||||
Id: "id-was-set",
|
||||
Name: "Default POSTed Rule",
|
||||
Flow: server.TrafficFlowBidirect,
|
||||
Flow: server.TrafficFlowBidirectString,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SaveRule PUT OK",
|
||||
requestType: http.MethodPut,
|
||||
name: "WriteRule POST Invalid Name",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/rules",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"ID":"id-existed","Name":"Default POSTed Rule","Flow":"bidirect"}`)),
|
||||
[]byte(`{"Name":"","Flow":"bidirect"}`)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "WriteRule PUT OK",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/rules/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":"Default POSTed Rule","Flow":"bidirect"}`)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedRule: &server.Rule{
|
||||
ID: "id-existed",
|
||||
expectedBody: true,
|
||||
expectedRule: &api.Rule{
|
||||
Id: "id-existed",
|
||||
Name: "Default POSTed Rule",
|
||||
Flow: server.TrafficFlowBidirect,
|
||||
Flow: server.TrafficFlowBidirectString,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WriteRule PUT Invalid Name",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/rules/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`{"Name":"","Flow":"bidirect"}`)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
},
|
||||
{
|
||||
name: "Write Rule PATCH Name OK",
|
||||
requestType: http.MethodPatch,
|
||||
requestPath: "/api/rules/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`[{"op":"replace","path":"name","value":["Default POSTed Rule"]}]`)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedRule: &api.Rule{
|
||||
Id: "id-existed",
|
||||
Name: "Default POSTed Rule",
|
||||
Flow: server.TrafficFlowBidirectString,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Write Rule PATCH Invalid Name OP",
|
||||
requestType: http.MethodPatch,
|
||||
requestPath: "/api/rules/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`[{"op":"insert","path":"name","value":[""]}]`)),
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "Write Rule PATCH Invalid Name",
|
||||
requestType: http.MethodPatch,
|
||||
requestPath: "/api/rules/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`[{"op":"replace","path":"name","value":[]}]`)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "Write Rule PATCH Sources OK",
|
||||
requestType: http.MethodPatch,
|
||||
requestPath: "/api/rules/id-existed",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(`[{"op":"replace","path":"sources","value":["G","F"]}]`)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedRule: &api.Rule{
|
||||
Id: "id-existed",
|
||||
Flow: server.TrafficFlowBidirectString,
|
||||
Sources: []api.GroupMinimum{
|
||||
{Id: "G"},
|
||||
{Id: "F"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -175,7 +273,9 @@ func TestRulesSaveRule(t *testing.T) {
|
||||
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/rules", p.CreateOrUpdateRuleHandler).Methods("PUT", "POST")
|
||||
router.HandleFunc("/api/rules", p.CreateRuleHandler).Methods("POST")
|
||||
router.HandleFunc("/api/rules/{id}", p.UpdateRuleHandler).Methods("PUT")
|
||||
router.HandleFunc("/api/rules/{id}", p.PatchRuleHandler).Methods("PATCH")
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
res := recorder.Result()
|
||||
@ -196,16 +296,13 @@ func TestRulesSaveRule(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
got := &RuleRequest{}
|
||||
got := &api.Rule{}
|
||||
if err = json.Unmarshal(content, &got); err != nil {
|
||||
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||
}
|
||||
|
||||
if tc.requestType != http.MethodPost {
|
||||
assert.Equal(t, got.ID, tc.expectedRule.ID)
|
||||
}
|
||||
assert.Equal(t, got.Name, tc.expectedRule.Name)
|
||||
assert.Equal(t, got.Flow, "bidirect")
|
||||
assert.Equal(t, got, tc.expectedRule)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2,56 +2,34 @@ package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SetupKeys is a handler that returns a list of setup keys of the account
|
||||
type SetupKeys struct {
|
||||
accountManager server.AccountManager
|
||||
jwtExtractor jwtclaims.ClaimsExtractor
|
||||
authAudience string
|
||||
}
|
||||
|
||||
// SetupKeyResponse is a response sent to the client
|
||||
type SetupKeyResponse struct {
|
||||
Id string
|
||||
Key string
|
||||
Name string
|
||||
Expires time.Time
|
||||
Type server.SetupKeyType
|
||||
Valid bool
|
||||
Revoked bool
|
||||
UsedTimes int
|
||||
LastUsed time.Time
|
||||
State string
|
||||
}
|
||||
|
||||
// SetupKeyRequest is a request sent by client. This object contains fields that can be modified
|
||||
type SetupKeyRequest struct {
|
||||
Name string
|
||||
Type server.SetupKeyType
|
||||
ExpiresIn *util.Duration
|
||||
Revoked bool
|
||||
}
|
||||
|
||||
func NewSetupKeysHandler(accountManager server.AccountManager, authAudience string) *SetupKeys {
|
||||
return &SetupKeys{
|
||||
accountManager: accountManager,
|
||||
authAudience: authAudience,
|
||||
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SetupKeys) updateKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
||||
req := &SetupKeyRequest{}
|
||||
req := &api.PutApiSetupKeysIdJSONRequestBody{}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
@ -96,19 +74,28 @@ func (h *SetupKeys) getKey(accountId string, keyId string, w http.ResponseWriter
|
||||
}
|
||||
|
||||
func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.Request) {
|
||||
req := &SetupKeyRequest{}
|
||||
req := &api.PostApiSetupKeysJSONRequestBody{}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !(req.Type == server.SetupKeyReusable || req.Type == server.SetupKeyOneOff) {
|
||||
if req.Name == "" {
|
||||
http.Error(w, "Setup key name shouldn't be empty", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
if !(server.SetupKeyType(req.Type) == server.SetupKeyReusable ||
|
||||
server.SetupKeyType(req.Type) == server.SetupKeyOneOff) {
|
||||
|
||||
http.Error(w, "unknown setup key type "+string(req.Type), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, req.Type, req.ExpiresIn)
|
||||
expiresIn := time.Duration(req.ExpiresIn) * time.Second
|
||||
|
||||
setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, server.SetupKeyType(req.Type), expiresIn)
|
||||
if err != nil {
|
||||
errStatus, ok := status.FromError(err)
|
||||
if ok && errStatus.Code() == codes.NotFound {
|
||||
@ -122,20 +109,8 @@ func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.R
|
||||
writeSuccess(w, setupKey)
|
||||
}
|
||||
|
||||
func (h *SetupKeys) getSetupKeyAccount(r *http.Request) (*server.Account, error) {
|
||||
extractor := jwtclaims.NewClaimsExtractor(nil)
|
||||
jwtClaims := extractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
|
||||
account, err := h.accountManager.GetAccountWithAuthorizationClaims(jwtClaims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
||||
account, err := h.getSetupKeyAccount(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
@ -163,7 +138,7 @@ func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
account, err := h.getSetupKeyAccount(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
@ -178,7 +153,7 @@ func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
respBody := []*SetupKeyResponse{}
|
||||
respBody := []*api.SetupKey{}
|
||||
for _, key := range account.SetupKeys {
|
||||
respBody = append(respBody, toResponseBody(key))
|
||||
}
|
||||
@ -204,7 +179,7 @@ func writeSuccess(w http.ResponseWriter, key *server.SetupKey) {
|
||||
}
|
||||
}
|
||||
|
||||
func toResponseBody(key *server.SetupKey) *SetupKeyResponse {
|
||||
func toResponseBody(key *server.SetupKey) *api.SetupKey {
|
||||
var state string
|
||||
if key.IsExpired() {
|
||||
state = "expired"
|
||||
@ -215,12 +190,12 @@ func toResponseBody(key *server.SetupKey) *SetupKeyResponse {
|
||||
} else {
|
||||
state = "valid"
|
||||
}
|
||||
return &SetupKeyResponse{
|
||||
return &api.SetupKey{
|
||||
Id: key.Id,
|
||||
Key: key.Key,
|
||||
Name: key.Name,
|
||||
Expires: key.ExpiresAt,
|
||||
Type: key.Type,
|
||||
Type: string(key.Type),
|
||||
Valid: key.IsValid(),
|
||||
Revoked: key.Revoked,
|
||||
UsedTimes: key.UsedTimes,
|
||||
|
@ -1,7 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
"net/http"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -16,13 +16,6 @@ type UserHandler struct {
|
||||
jwtExtractor jwtclaims.ClaimsExtractor
|
||||
}
|
||||
|
||||
type UserResponse struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
func NewUserHandler(accountManager server.AccountManager, authAudience string) *UserHandler {
|
||||
return &UserHandler{
|
||||
accountManager: accountManager,
|
||||
@ -31,37 +24,26 @@ func NewUserHandler(accountManager server.AccountManager, authAudience string) *
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserHandler) getAccountId(r *http.Request) (*server.Account, error) {
|
||||
jwtClaims := u.jwtExtractor.ExtractClaimsFromRequestContext(r, u.authAudience)
|
||||
|
||||
account, err := u.accountManager.GetAccountWithAuthorizationClaims(jwtClaims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// GetUsers returns a list of users of the account this user belongs to.
|
||||
// It also gathers additional user data (like email and name) from the IDP manager.
|
||||
func (u *UserHandler) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *UserHandler) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
account, err := u.getAccountId(r)
|
||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
data, err := u.accountManager.GetUsersFromAccount(account.Id)
|
||||
data, err := h.accountManager.GetUsersFromAccount(account.Id)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
users := []*UserResponse{}
|
||||
users := []*api.User{}
|
||||
for _, r := range data {
|
||||
users = append(users, toUserResponse(r))
|
||||
}
|
||||
@ -69,9 +51,9 @@ func (u *UserHandler) GetUsers(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSONObject(w, users)
|
||||
}
|
||||
|
||||
func toUserResponse(user *server.UserInfo) *UserResponse {
|
||||
return &UserResponse{
|
||||
ID: user.ID,
|
||||
func toUserResponse(user *server.UserInfo) *api.User {
|
||||
return &api.User{
|
||||
Id: user.ID,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
Role: user.Role,
|
||||
|
@ -3,6 +3,9 @@ package handler
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
@ -47,3 +50,17 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||
return errors.New("invalid duration")
|
||||
}
|
||||
}
|
||||
|
||||
func getJWTAccount(accountManager server.AccountManager,
|
||||
jwtExtractor jwtclaims.ClaimsExtractor,
|
||||
authAudience string, r *http.Request) (*server.Account, error) {
|
||||
|
||||
jwtClaims := jwtExtractor.ExtractClaimsFromRequestContext(r, authAudience)
|
||||
|
||||
account, err := accountManager.GetAccountWithAuthorizationClaims(jwtClaims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting account of a user %s: %v", jwtClaims.UserId, err)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
@ -103,13 +103,12 @@ func (s *Server) Start() error {
|
||||
rulesHandler := handler.NewRules(s.accountManager, s.config.AuthAudience)
|
||||
peersHandler := handler.NewPeers(s.accountManager, s.config.AuthAudience)
|
||||
keysHandler := handler.NewSetupKeysHandler(s.accountManager, s.config.AuthAudience)
|
||||
userHandler := handler.NewUserHandler(s.accountManager, s.config.AuthAudience)
|
||||
|
||||
r.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).
|
||||
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
||||
|
||||
userHandler := handler.NewUserHandler(s.accountManager, s.config.AuthAudience)
|
||||
r.HandleFunc("/api/users", userHandler.GetUsers).Methods("GET", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("GET", "POST", "OPTIONS")
|
||||
r.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).Methods("GET", "PUT", "OPTIONS")
|
||||
|
||||
@ -118,14 +117,16 @@ func (s *Server) Start() error {
|
||||
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/rules", rulesHandler.GetAllRulesHandler).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/rules", rulesHandler.CreateOrUpdateRuleHandler).
|
||||
Methods("POST", "PUT", "OPTIONS")
|
||||
r.HandleFunc("/api/rules", rulesHandler.CreateRuleHandler).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/rules/{id}", rulesHandler.UpdateRuleHandler).Methods("PUT", "OPTIONS")
|
||||
r.HandleFunc("/api/rules/{id}", rulesHandler.PatchRuleHandler).Methods("PATCH", "OPTIONS")
|
||||
r.HandleFunc("/api/rules/{id}", rulesHandler.GetRuleHandler).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/rules/{id}", rulesHandler.DeleteRuleHandler).Methods("DELETE", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/groups", groupsHandler.GetAllGroupsHandler).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/groups", groupsHandler.CreateOrUpdateGroupHandler).
|
||||
Methods("POST", "PUT", "OPTIONS")
|
||||
r.HandleFunc("/api/groups", groupsHandler.CreateGroupHandler).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/groups/{id}", groupsHandler.UpdateGroupHandler).Methods("PUT", "OPTIONS")
|
||||
r.HandleFunc("/api/groups/{id}", groupsHandler.PatchGroupHandler).Methods("PATCH", "OPTIONS")
|
||||
r.HandleFunc("/api/groups/{id}", groupsHandler.GetGroupHandler).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/groups/{id}", groupsHandler.DeleteGroupHandler).Methods("DELETE", "OPTIONS")
|
||||
http.Handle("/", r)
|
||||
|
@ -36,6 +36,9 @@ func NewClaimsExtractor(e ExtractClaims) *ClaimsExtractor {
|
||||
|
||||
// ExtractClaimsFromRequestContext extracts claims from the request context previously filled by the JWT token (after auth)
|
||||
func ExtractClaimsFromRequestContext(r *http.Request, authAudience string) AuthorizationClaims {
|
||||
if r.Context().Value(TokenUserProperty) == nil {
|
||||
return AuthorizationClaims{}
|
||||
}
|
||||
token := r.Context().Value(TokenUserProperty).(*jwt.Token)
|
||||
return ExtractClaimsWithToken(token, authAudience)
|
||||
}
|
||||
|
@ -3,15 +3,15 @@ package mock_server
|
||||
import (
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MockAccountManager struct {
|
||||
GetOrCreateAccountByUserFunc func(userId, domain string) (*server.Account, error)
|
||||
GetAccountByUserFunc func(userId string) (*server.Account, error)
|
||||
AddSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType, expiresIn *util.Duration) (*server.SetupKey, error)
|
||||
AddSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType, expiresIn time.Duration) (*server.SetupKey, error)
|
||||
RevokeSetupKeyFunc func(accountId string, keyId string) (*server.SetupKey, error)
|
||||
RenameSetupKeyFunc func(accountId string, keyId string, newName string) (*server.SetupKey, error)
|
||||
GetAccountByIdFunc func(accountId string) (*server.Account, error)
|
||||
@ -28,6 +28,7 @@ type MockAccountManager struct {
|
||||
AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, error)
|
||||
GetGroupFunc func(accountID, groupID string) (*server.Group, error)
|
||||
SaveGroupFunc func(accountID string, group *server.Group) error
|
||||
UpdateGroupFunc func(accountID string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error)
|
||||
DeleteGroupFunc func(accountID, groupID string) error
|
||||
ListGroupsFunc func(accountID string) ([]*server.Group, error)
|
||||
GroupAddPeerFunc func(accountID, groupID, peerKey string) error
|
||||
@ -35,12 +36,14 @@ type MockAccountManager struct {
|
||||
GroupListPeersFunc func(accountID, groupID string) ([]*server.Peer, error)
|
||||
GetRuleFunc func(accountID, ruleID string) (*server.Rule, error)
|
||||
SaveRuleFunc func(accountID string, rule *server.Rule) error
|
||||
UpdateRuleFunc func(accountID string, ruleID string, operations []server.RuleUpdateOperation) (*server.Rule, error)
|
||||
DeleteRuleFunc func(accountID, ruleID string) error
|
||||
ListRulesFunc func(accountID string) ([]*server.Rule, error)
|
||||
GetUsersFromAccountFunc func(accountID string) ([]*server.UserInfo, error)
|
||||
UpdatePeerMetaFunc func(peerKey string, meta server.PeerSystemMeta) error
|
||||
}
|
||||
|
||||
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetUsersFromAccount(accountID string) ([]*server.UserInfo, error) {
|
||||
if am.GetUsersFromAccountFunc != nil {
|
||||
return am.GetUsersFromAccountFunc(accountID)
|
||||
@ -48,6 +51,7 @@ func (am *MockAccountManager) GetUsersFromAccount(accountID string) ([]*server.U
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetUsersFromAccount not implemented")
|
||||
}
|
||||
|
||||
// GetOrCreateAccountByUser mock implementation of GetOrCreateAccountByUser from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetOrCreateAccountByUser(
|
||||
userId, domain string,
|
||||
) (*server.Account, error) {
|
||||
@ -60,6 +64,7 @@ func (am *MockAccountManager) GetOrCreateAccountByUser(
|
||||
)
|
||||
}
|
||||
|
||||
// GetAccountByUser mock implementation of GetAccountByUser from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetAccountByUser(userId string) (*server.Account, error) {
|
||||
if am.GetAccountByUserFunc != nil {
|
||||
return am.GetAccountByUserFunc(userId)
|
||||
@ -67,11 +72,12 @@ func (am *MockAccountManager) GetAccountByUser(userId string) (*server.Account,
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser not implemented")
|
||||
}
|
||||
|
||||
// AddSetupKey mock implementation of AddSetupKey from server.AccountManager interface
|
||||
func (am *MockAccountManager) AddSetupKey(
|
||||
accountId string,
|
||||
keyName string,
|
||||
keyType server.SetupKeyType,
|
||||
expiresIn *util.Duration,
|
||||
expiresIn time.Duration,
|
||||
) (*server.SetupKey, error) {
|
||||
if am.AddSetupKeyFunc != nil {
|
||||
return am.AddSetupKeyFunc(accountId, keyName, keyType, expiresIn)
|
||||
@ -79,6 +85,7 @@ func (am *MockAccountManager) AddSetupKey(
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddSetupKey not implemented")
|
||||
}
|
||||
|
||||
// RevokeSetupKey mock implementation of RevokeSetupKey from server.AccountManager interface
|
||||
func (am *MockAccountManager) RevokeSetupKey(
|
||||
accountId string,
|
||||
keyId string,
|
||||
@ -89,6 +96,7 @@ func (am *MockAccountManager) RevokeSetupKey(
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RevokeSetupKey not implemented")
|
||||
}
|
||||
|
||||
// RenameSetupKey mock implementation of RenameSetupKey from server.AccountManager interface
|
||||
func (am *MockAccountManager) RenameSetupKey(
|
||||
accountId string,
|
||||
keyId string,
|
||||
@ -100,6 +108,7 @@ func (am *MockAccountManager) RenameSetupKey(
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RenameSetupKey not implemented")
|
||||
}
|
||||
|
||||
// GetAccountById mock implementation of GetAccountById from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetAccountById(accountId string) (*server.Account, error) {
|
||||
if am.GetAccountByIdFunc != nil {
|
||||
return am.GetAccountByIdFunc(accountId)
|
||||
@ -107,6 +116,7 @@ func (am *MockAccountManager) GetAccountById(accountId string) (*server.Account,
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountById not implemented")
|
||||
}
|
||||
|
||||
// GetAccountByUserOrAccountId mock implementation of GetAccountByUserOrAccountId from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetAccountByUserOrAccountId(
|
||||
userId, accountId, domain string,
|
||||
) (*server.Account, error) {
|
||||
@ -119,6 +129,7 @@ func (am *MockAccountManager) GetAccountByUserOrAccountId(
|
||||
)
|
||||
}
|
||||
|
||||
// GetAccountWithAuthorizationClaims mock implementation of GetAccountWithAuthorizationClaims from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetAccountWithAuthorizationClaims(
|
||||
claims jwtclaims.AuthorizationClaims,
|
||||
) (*server.Account, error) {
|
||||
@ -131,6 +142,7 @@ func (am *MockAccountManager) GetAccountWithAuthorizationClaims(
|
||||
)
|
||||
}
|
||||
|
||||
// AccountExists mock implementation of AccountExists from server.AccountManager interface
|
||||
func (am *MockAccountManager) AccountExists(accountId string) (*bool, error) {
|
||||
if am.AccountExistsFunc != nil {
|
||||
return am.AccountExistsFunc(accountId)
|
||||
@ -138,6 +150,7 @@ func (am *MockAccountManager) AccountExists(accountId string) (*bool, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AccountExists not implemented")
|
||||
}
|
||||
|
||||
// GetPeer mock implementation of GetPeer from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetPeer(peerKey string) (*server.Peer, error) {
|
||||
if am.GetPeerFunc != nil {
|
||||
return am.GetPeerFunc(peerKey)
|
||||
@ -145,6 +158,7 @@ func (am *MockAccountManager) GetPeer(peerKey string) (*server.Peer, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeer not implemented")
|
||||
}
|
||||
|
||||
// MarkPeerConnected mock implementation of MarkPeerConnected from server.AccountManager interface
|
||||
func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool) error {
|
||||
if am.MarkPeerConnectedFunc != nil {
|
||||
return am.MarkPeerConnectedFunc(peerKey, connected)
|
||||
@ -152,6 +166,7 @@ func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool)
|
||||
return status.Errorf(codes.Unimplemented, "method MarkPeerConnected not implemented")
|
||||
}
|
||||
|
||||
// RenamePeer mock implementation of RenamePeer from server.AccountManager interface
|
||||
func (am *MockAccountManager) RenamePeer(
|
||||
accountId string,
|
||||
peerKey string,
|
||||
@ -163,6 +178,7 @@ func (am *MockAccountManager) RenamePeer(
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RenamePeer not implemented")
|
||||
}
|
||||
|
||||
// DeletePeer mock implementation of DeletePeer from server.AccountManager interface
|
||||
func (am *MockAccountManager) DeletePeer(accountId string, peerKey string) (*server.Peer, error) {
|
||||
if am.DeletePeerFunc != nil {
|
||||
return am.DeletePeerFunc(accountId, peerKey)
|
||||
@ -170,6 +186,7 @@ func (am *MockAccountManager) DeletePeer(accountId string, peerKey string) (*ser
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer not implemented")
|
||||
}
|
||||
|
||||
// GetPeerByIP mock implementation of GetPeerByIP from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*server.Peer, error) {
|
||||
if am.GetPeerByIPFunc != nil {
|
||||
return am.GetPeerByIPFunc(accountId, peerIP)
|
||||
@ -177,6 +194,7 @@ func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*ser
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeerByIP not implemented")
|
||||
}
|
||||
|
||||
// GetNetworkMap mock implementation of GetNetworkMap from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetNetworkMap(peerKey string) (*server.NetworkMap, error) {
|
||||
if am.GetNetworkMapFunc != nil {
|
||||
return am.GetNetworkMapFunc(peerKey)
|
||||
@ -184,6 +202,7 @@ func (am *MockAccountManager) GetNetworkMap(peerKey string) (*server.NetworkMap,
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetNetworkMap not implemented")
|
||||
}
|
||||
|
||||
// AddPeer mock implementation of AddPeer from server.AccountManager interface
|
||||
func (am *MockAccountManager) AddPeer(
|
||||
setupKey string,
|
||||
userId string,
|
||||
@ -195,6 +214,7 @@ func (am *MockAccountManager) AddPeer(
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddPeer not implemented")
|
||||
}
|
||||
|
||||
// GetGroup mock implementation of GetGroup from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetGroup(accountID, groupID string) (*server.Group, error) {
|
||||
if am.GetGroupFunc != nil {
|
||||
return am.GetGroupFunc(accountID, groupID)
|
||||
@ -202,6 +222,7 @@ func (am *MockAccountManager) GetGroup(accountID, groupID string) (*server.Group
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetGroup not implemented")
|
||||
}
|
||||
|
||||
// SaveGroup mock implementation of SaveGroup from server.AccountManager interface
|
||||
func (am *MockAccountManager) SaveGroup(accountID string, group *server.Group) error {
|
||||
if am.SaveGroupFunc != nil {
|
||||
return am.SaveGroupFunc(accountID, group)
|
||||
@ -209,6 +230,15 @@ func (am *MockAccountManager) SaveGroup(accountID string, group *server.Group) e
|
||||
return status.Errorf(codes.Unimplemented, "method SaveGroup not implemented")
|
||||
}
|
||||
|
||||
// UpdateGroup mock implementation of UpdateGroup from server.AccountManager interface
|
||||
func (am *MockAccountManager) UpdateGroup(accountID string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error) {
|
||||
if am.UpdateGroupFunc != nil {
|
||||
return am.UpdateGroupFunc(accountID, groupID, operations)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateGroup not implemented")
|
||||
}
|
||||
|
||||
// DeleteGroup mock implementation of DeleteGroup from server.AccountManager interface
|
||||
func (am *MockAccountManager) DeleteGroup(accountID, groupID string) error {
|
||||
if am.DeleteGroupFunc != nil {
|
||||
return am.DeleteGroupFunc(accountID, groupID)
|
||||
@ -216,6 +246,7 @@ func (am *MockAccountManager) DeleteGroup(accountID, groupID string) error {
|
||||
return status.Errorf(codes.Unimplemented, "method DeleteGroup not implemented")
|
||||
}
|
||||
|
||||
// ListGroups mock implementation of ListGroups from server.AccountManager interface
|
||||
func (am *MockAccountManager) ListGroups(accountID string) ([]*server.Group, error) {
|
||||
if am.ListGroupsFunc != nil {
|
||||
return am.ListGroupsFunc(accountID)
|
||||
@ -223,6 +254,7 @@ func (am *MockAccountManager) ListGroups(accountID string) ([]*server.Group, err
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListGroups not implemented")
|
||||
}
|
||||
|
||||
// GroupAddPeer mock implementation of GroupAddPeer from server.AccountManager interface
|
||||
func (am *MockAccountManager) GroupAddPeer(accountID, groupID, peerKey string) error {
|
||||
if am.GroupAddPeerFunc != nil {
|
||||
return am.GroupAddPeerFunc(accountID, groupID, peerKey)
|
||||
@ -230,6 +262,7 @@ func (am *MockAccountManager) GroupAddPeer(accountID, groupID, peerKey string) e
|
||||
return status.Errorf(codes.Unimplemented, "method GroupAddPeer not implemented")
|
||||
}
|
||||
|
||||
// GroupDeletePeer mock implementation of GroupDeletePeer from server.AccountManager interface
|
||||
func (am *MockAccountManager) GroupDeletePeer(accountID, groupID, peerKey string) error {
|
||||
if am.GroupDeletePeerFunc != nil {
|
||||
return am.GroupDeletePeerFunc(accountID, groupID, peerKey)
|
||||
@ -237,6 +270,7 @@ func (am *MockAccountManager) GroupDeletePeer(accountID, groupID, peerKey string
|
||||
return status.Errorf(codes.Unimplemented, "method GroupDeletePeer not implemented")
|
||||
}
|
||||
|
||||
// GroupListPeers mock implementation of GroupListPeers from server.AccountManager interface
|
||||
func (am *MockAccountManager) GroupListPeers(accountID, groupID string) ([]*server.Peer, error) {
|
||||
if am.GroupListPeersFunc != nil {
|
||||
return am.GroupListPeersFunc(accountID, groupID)
|
||||
@ -244,6 +278,7 @@ func (am *MockAccountManager) GroupListPeers(accountID, groupID string) ([]*serv
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GroupListPeers not implemented")
|
||||
}
|
||||
|
||||
// GetRule mock implementation of GetRule from server.AccountManager interface
|
||||
func (am *MockAccountManager) GetRule(accountID, ruleID string) (*server.Rule, error) {
|
||||
if am.GetRuleFunc != nil {
|
||||
return am.GetRuleFunc(accountID, ruleID)
|
||||
@ -251,6 +286,7 @@ func (am *MockAccountManager) GetRule(accountID, ruleID string) (*server.Rule, e
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetRule not implemented")
|
||||
}
|
||||
|
||||
// SaveRule mock implementation of SaveRule from server.AccountManager interface
|
||||
func (am *MockAccountManager) SaveRule(accountID string, rule *server.Rule) error {
|
||||
if am.SaveRuleFunc != nil {
|
||||
return am.SaveRuleFunc(accountID, rule)
|
||||
@ -258,6 +294,15 @@ func (am *MockAccountManager) SaveRule(accountID string, rule *server.Rule) erro
|
||||
return status.Errorf(codes.Unimplemented, "method SaveRule not implemented")
|
||||
}
|
||||
|
||||
// UpdateRule mock implementation of UpdateRule from server.AccountManager interface
|
||||
func (am *MockAccountManager) UpdateRule(accountID string, ruleID string, operations []server.RuleUpdateOperation) (*server.Rule, error) {
|
||||
if am.UpdateRuleFunc != nil {
|
||||
return am.UpdateRuleFunc(accountID, ruleID, operations)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateRule not implemented")
|
||||
}
|
||||
|
||||
// DeleteRule mock implementation of DeleteRule from server.AccountManager interface
|
||||
func (am *MockAccountManager) DeleteRule(accountID, ruleID string) error {
|
||||
if am.DeleteRuleFunc != nil {
|
||||
return am.DeleteRuleFunc(accountID, ruleID)
|
||||
@ -265,6 +310,7 @@ func (am *MockAccountManager) DeleteRule(accountID, ruleID string) error {
|
||||
return status.Errorf(codes.Unimplemented, "method DeleteRule not implemented")
|
||||
}
|
||||
|
||||
// ListRules mock implementation of ListRules from server.AccountManager interface
|
||||
func (am *MockAccountManager) ListRules(accountID string) ([]*server.Rule, error) {
|
||||
if am.ListRulesFunc != nil {
|
||||
return am.ListRulesFunc(accountID)
|
||||
@ -272,6 +318,7 @@ func (am *MockAccountManager) ListRules(accountID string) ([]*server.Rule, error
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListRules not implemented")
|
||||
}
|
||||
|
||||
// UpdatePeerMeta mock implementation of UpdatePeerMeta from server.AccountManager interface
|
||||
func (am *MockAccountManager) UpdatePeerMeta(peerKey string, meta server.PeerSystemMeta) error {
|
||||
if am.UpdatePeerMetaFunc != nil {
|
||||
return am.UpdatePeerMetaFunc(peerKey, meta)
|
||||
@ -279,6 +326,7 @@ func (am *MockAccountManager) UpdatePeerMeta(peerKey string, meta server.PeerSys
|
||||
return status.Errorf(codes.Unimplemented, "method UpdatePeerMetaFunc not implemented")
|
||||
}
|
||||
|
||||
// IsUserAdmin mock implementation of IsUserAdmin from server.AccountManager interface
|
||||
func (am *MockAccountManager) IsUserAdmin(claims jwtclaims.AuthorizationClaims) (bool, error) {
|
||||
if am.IsUserAdminFunc != nil {
|
||||
return am.IsUserAdminFunc(claims)
|
||||
|
@ -360,6 +360,9 @@ func (am *DefaultAccountManager) getPeersByACL(account *Account, peerKey string)
|
||||
|
||||
groups := map[string]*Group{}
|
||||
for _, r := range srcRules {
|
||||
if r.Disabled {
|
||||
continue
|
||||
}
|
||||
if r.Flow == TrafficFlowBidirect {
|
||||
for _, gid := range r.Destination {
|
||||
if group, ok := account.Groups[gid]; ok {
|
||||
@ -370,6 +373,9 @@ func (am *DefaultAccountManager) getPeersByACL(account *Account, peerKey string)
|
||||
}
|
||||
|
||||
for _, r := range dstRules {
|
||||
if r.Disabled {
|
||||
continue
|
||||
}
|
||||
if r.Flow == TrafficFlowBidirect {
|
||||
for _, gid := range r.Source {
|
||||
if group, ok := account.Groups[gid]; ok {
|
||||
|
@ -220,4 +220,36 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
|
||||
networkMap2.Peers[0].Key,
|
||||
)
|
||||
}
|
||||
|
||||
rule.Disabled = true
|
||||
err = manager.SaveRule(account.Id, &rule)
|
||||
if err != nil {
|
||||
t.Errorf("expecting rule to be added, got failure %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
networkMap1, err = manager.GetNetworkMap(peerKey1.PublicKey().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(networkMap1.Peers) != 0 {
|
||||
t.Errorf(
|
||||
"expecting Account NetworkMap to have 0 peers, got %v: %v",
|
||||
len(networkMap1.Peers),
|
||||
networkMap1.Peers,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
networkMap2, err = manager.GetNetworkMap(peerKey2.PublicKey().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(networkMap2.Peers) != 0 {
|
||||
t.Errorf("expecting Account NetworkMap to have 0 peers, got %v", len(networkMap2.Peers))
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TrafficFlowType defines allowed direction of the traffic in the rule
|
||||
@ -11,6 +12,12 @@ type TrafficFlowType int
|
||||
const (
|
||||
// TrafficFlowBidirect allows traffic to both direction
|
||||
TrafficFlowBidirect TrafficFlowType = iota
|
||||
// TrafficFlowBidirectString allows traffic to both direction
|
||||
TrafficFlowBidirectString = "bidirect"
|
||||
// DefaultRuleName is a name for the Default rule that is created for every account
|
||||
DefaultRuleName = "Default"
|
||||
// DefaultRuleDescription is a description for the Default rule that is created for every account
|
||||
DefaultRuleDescription = "This is a default rule that allows connections between all the resources"
|
||||
)
|
||||
|
||||
// Rule of ACL for groups
|
||||
@ -21,6 +28,12 @@ type Rule struct {
|
||||
// Name of the rule visible in the UI
|
||||
Name string
|
||||
|
||||
// Description of the rule visible in the UI
|
||||
Description string
|
||||
|
||||
// Disabled status of rule in the system
|
||||
Disabled bool
|
||||
|
||||
// Source list of groups IDs of peers
|
||||
Source []string
|
||||
|
||||
@ -31,10 +44,44 @@ type Rule struct {
|
||||
Flow TrafficFlowType
|
||||
}
|
||||
|
||||
const (
|
||||
// UpdateRuleName indicates a rule name update operation
|
||||
UpdateRuleName RuleUpdateOperationType = iota
|
||||
// UpdateRuleDescription indicates a rule description update operation
|
||||
UpdateRuleDescription
|
||||
// UpdateRuleStatus indicates a rule status update operation
|
||||
UpdateRuleStatus
|
||||
// UpdateRuleFlow indicates a rule flow update operation
|
||||
UpdateRuleFlow
|
||||
// InsertGroupsToSource indicates an insert groups to source rule operation
|
||||
InsertGroupsToSource
|
||||
// RemoveGroupsFromSource indicates an remove groups from source rule operation
|
||||
RemoveGroupsFromSource
|
||||
// UpdateSourceGroups indicates a replacement of source group list of a rule operation
|
||||
UpdateSourceGroups
|
||||
// InsertGroupsToDestination indicates an insert groups to destination rule operation
|
||||
InsertGroupsToDestination
|
||||
// RemoveGroupsFromDestination indicates an remove groups from destination rule operation
|
||||
RemoveGroupsFromDestination
|
||||
// UpdateDestinationGroups indicates a replacement of destination group list of a rule operation
|
||||
UpdateDestinationGroups
|
||||
)
|
||||
|
||||
// RuleUpdateOperationType operation type
|
||||
type RuleUpdateOperationType int
|
||||
|
||||
// RuleUpdateOperation operation object with type and values to be applied
|
||||
type RuleUpdateOperation struct {
|
||||
Type RuleUpdateOperationType
|
||||
Values []string
|
||||
}
|
||||
|
||||
func (r *Rule) Copy() *Rule {
|
||||
return &Rule{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
Disabled: r.Disabled,
|
||||
Source: r.Source[:],
|
||||
Destination: r.Destination[:],
|
||||
Flow: r.Flow,
|
||||
@ -79,6 +126,81 @@ func (am *DefaultAccountManager) SaveRule(accountID string, rule *Rule) error {
|
||||
return am.updateAccountPeers(account)
|
||||
}
|
||||
|
||||
// UpdateRule updates a rule using a list of operations
|
||||
func (am *DefaultAccountManager) UpdateRule(accountID string, ruleID string,
|
||||
operations []RuleUpdateOperation) (*Rule, error) {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||
}
|
||||
|
||||
ruleToUpdate, ok := account.Rules[ruleID]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "rule %s no longer exists", ruleID)
|
||||
}
|
||||
|
||||
rule := ruleToUpdate.Copy()
|
||||
|
||||
for _, operation := range operations {
|
||||
switch operation.Type {
|
||||
case UpdateRuleName:
|
||||
rule.Name = operation.Values[0]
|
||||
case UpdateRuleDescription:
|
||||
rule.Description = operation.Values[0]
|
||||
case UpdateRuleFlow:
|
||||
if operation.Values[0] != TrafficFlowBidirectString {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "failed to parse flow")
|
||||
}
|
||||
rule.Flow = TrafficFlowBidirect
|
||||
case UpdateRuleStatus:
|
||||
if strings.ToLower(operation.Values[0]) == "true" {
|
||||
rule.Disabled = true
|
||||
} else if strings.ToLower(operation.Values[0]) == "false" {
|
||||
rule.Disabled = false
|
||||
} else {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "failed to parse status")
|
||||
}
|
||||
case UpdateSourceGroups:
|
||||
rule.Source = operation.Values
|
||||
case InsertGroupsToSource:
|
||||
sourceList := rule.Source
|
||||
resultList := removeFromList(sourceList, operation.Values)
|
||||
rule.Source = append(resultList, operation.Values...)
|
||||
case RemoveGroupsFromSource:
|
||||
sourceList := rule.Source
|
||||
resultList := removeFromList(sourceList, operation.Values)
|
||||
rule.Source = resultList
|
||||
case UpdateDestinationGroups:
|
||||
rule.Destination = operation.Values
|
||||
case InsertGroupsToDestination:
|
||||
sourceList := rule.Destination
|
||||
resultList := removeFromList(sourceList, operation.Values)
|
||||
rule.Destination = append(resultList, operation.Values...)
|
||||
case RemoveGroupsFromDestination:
|
||||
sourceList := rule.Destination
|
||||
resultList := removeFromList(sourceList, operation.Values)
|
||||
rule.Destination = resultList
|
||||
}
|
||||
}
|
||||
|
||||
account.Rules[ruleID] = rule
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = am.updateAccountPeers(account)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to update account peers")
|
||||
}
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
// DeleteRule of ACL from the store
|
||||
func (am *DefaultAccountManager) DeleteRule(accountID, ruleID string) error {
|
||||
am.mux.Lock()
|
||||
|
Loading…
Reference in New Issue
Block a user