mirror of
https://github.com/netbirdio/netbird.git
synced 2025-02-13 08:50:32 +01:00
Link account id with the external user store (#184)
* get account id from access token claim * use GetOrCreateAccountByUser and add test * correct account id claim * remove unused account * Idp manager interface * auth0 idp manager * use if instead of switch case * remove unnecessary lock * NewAuth0Manager * move idpmanager to its own package * update metadata when accountId is not supplied * update tests with idpmanager field * format * new idp manager and config support * validate if we fetch the interface before converting to string * split getJWTToken * improve tests * proper json fields and handle defer body close * fix ci lint notes * documentation and proper defer position * UpdateUserAppMetadata tests * update documentation * ManagerCredentials interface * Marshal and Unmarshal functions * fix tests * ManagerHelper and ManagerHTTPClient * further tests with mocking * rename package and custom http client * sync local packages * remove idp suffix
This commit is contained in:
parent
2ad899b066
commit
fd7282d3cf
@ -38,7 +38,7 @@ func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Liste
|
|||||||
}
|
}
|
||||||
|
|
||||||
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||||
accountManager := mgmt.NewManager(store, peersUpdateManager)
|
accountManager := mgmt.NewManager(store, peersUpdateManager, nil)
|
||||||
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -442,7 +442,7 @@ func startManagement(port int, dataDir string) (*grpc.Server, error) {
|
|||||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||||
}
|
}
|
||||||
peersUpdateManager := server.NewPeersUpdateManager()
|
peersUpdateManager := server.NewPeersUpdateManager()
|
||||||
accountManager := server.NewManager(store, peersUpdateManager)
|
accountManager := server.NewManager(store, peersUpdateManager, nil)
|
||||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
1
go.sum
1
go.sum
@ -148,7 +148,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
|||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
@ -35,5 +35,15 @@
|
|||||||
"AuthIssuer": "https://$WIRETRUSTEE_AUTH0_DOMAIN/",
|
"AuthIssuer": "https://$WIRETRUSTEE_AUTH0_DOMAIN/",
|
||||||
"AuthAudience": "$WIRETRUSTEE_AUTH0_AUDIENCE",
|
"AuthAudience": "$WIRETRUSTEE_AUTH0_AUDIENCE",
|
||||||
"AuthKeysLocation": "https://$WIRETRUSTEE_AUTH0_DOMAIN/.well-known/jwks.json"
|
"AuthKeysLocation": "https://$WIRETRUSTEE_AUTH0_DOMAIN/.well-known/jwks.json"
|
||||||
}
|
},
|
||||||
|
"IdpManagerConfig": {
|
||||||
|
"Manager": "none",
|
||||||
|
"Auth0ClientCredentials": {
|
||||||
|
"Audience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
||||||
|
"AuthIssuer": "<PASTE YOUR AUTH0 Auth Issuer HERE>",
|
||||||
|
"ClientId": "<PASTE YOUR AUTH0 Application Client ID HERE>",
|
||||||
|
"ClientSecret": "<PASTE YOUR AUTH0 Application Client Secret HERE>",
|
||||||
|
"GrantType": "client_credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -45,7 +45,7 @@ docker run -d --name wiretrustee-management \
|
|||||||
wiretrustee/management:latest \
|
wiretrustee/management:latest \
|
||||||
--letsencrypt-domain <YOUR-DOMAIN>
|
--letsencrypt-domain <YOUR-DOMAIN>
|
||||||
```
|
```
|
||||||
> An example of config.json can be found here [config.json](../infrastructure_files/config.json)
|
> An example of config.json can be found here [management.json](../infrastructure_files/management.json.tmpl)
|
||||||
|
|
||||||
Trigger Let's encrypt certificate generation:
|
Trigger Let's encrypt certificate generation:
|
||||||
```bash
|
```bash
|
||||||
|
@ -61,7 +61,7 @@ func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Liste
|
|||||||
}
|
}
|
||||||
|
|
||||||
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||||
accountManager := mgmt.NewManager(store, peersUpdateManager)
|
accountManager := mgmt.NewManager(store, peersUpdateManager, nil)
|
||||||
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/wiretrustee/wiretrustee/management/server"
|
"github.com/wiretrustee/wiretrustee/management/server"
|
||||||
"github.com/wiretrustee/wiretrustee/management/server/http"
|
"github.com/wiretrustee/wiretrustee/management/server/http"
|
||||||
|
"github.com/wiretrustee/wiretrustee/management/server/idp"
|
||||||
"github.com/wiretrustee/wiretrustee/util"
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@ -68,7 +69,11 @@ var (
|
|||||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||||
}
|
}
|
||||||
peersUpdateManager := server.NewPeersUpdateManager()
|
peersUpdateManager := server.NewPeersUpdateManager()
|
||||||
accountManager := server.NewManager(store, peersUpdateManager)
|
idpManager, err := idp.NewManager(*config.IdpManagerConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("failed retrieving a new idp manager with err: ", err)
|
||||||
|
}
|
||||||
|
accountManager := server.NewManager(store, peersUpdateManager, idpManager)
|
||||||
|
|
||||||
var opts []grpc.ServerOption
|
var opts []grpc.ServerOption
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/wiretrustee/wiretrustee/management/server/idp"
|
||||||
"github.com/wiretrustee/wiretrustee/util"
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
@ -14,6 +15,7 @@ type AccountManager struct {
|
|||||||
// mutex to synchronise account operations (e.g. generating Peer IP address inside the Network)
|
// mutex to synchronise account operations (e.g. generating Peer IP address inside the Network)
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
peersUpdateManager *PeersUpdateManager
|
peersUpdateManager *PeersUpdateManager
|
||||||
|
idpManager idp.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account represents a unique account of the system
|
// Account represents a unique account of the system
|
||||||
@ -60,11 +62,12 @@ func (a *Account) Copy() *Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a new AccountManager with a provided Store
|
// NewManager creates a new AccountManager with a provided Store
|
||||||
func NewManager(store Store, peersUpdateManager *PeersUpdateManager) *AccountManager {
|
func NewManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager) *AccountManager {
|
||||||
return &AccountManager{
|
return &AccountManager{
|
||||||
Store: store,
|
Store: store,
|
||||||
mux: sync.Mutex{},
|
mux: sync.Mutex{},
|
||||||
peersUpdateManager: peersUpdateManager,
|
peersUpdateManager: peersUpdateManager,
|
||||||
|
idpManager: idpManager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +162,30 @@ func (am *AccountManager) GetAccount(accountId string) (*Account, error) {
|
|||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//GetAccountByUserOrAccountId look for an account by user or account Id, if no account is provided and
|
||||||
|
// user id doesn't have an account associated with it, one account is created
|
||||||
|
func (am *AccountManager) GetAccountByUserOrAccountId(userId, accountId string) (*Account, error) {
|
||||||
|
|
||||||
|
if accountId != "" {
|
||||||
|
return am.GetAccount(accountId)
|
||||||
|
} else if userId != "" {
|
||||||
|
account, err := am.GetOrCreateAccountByUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "account not found using user id: %s", userId)
|
||||||
|
}
|
||||||
|
// update idp manager app metadata
|
||||||
|
if am.idpManager != nil {
|
||||||
|
err = am.idpManager.UpdateUserAppMetadata(userId, idp.AppMetadata{WTAccountId: account.Id})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "updating user's app metadata failed with: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, status.Errorf(codes.NotFound, "no valid user or account Id provided")
|
||||||
|
}
|
||||||
|
|
||||||
//AccountExists checks whether account exists (returns true) or not (returns false)
|
//AccountExists checks whether account exists (returns true) or not (returns false)
|
||||||
func (am *AccountManager) AccountExists(accountId string) (*bool, error) {
|
func (am *AccountManager) AccountExists(accountId string) (*bool, error) {
|
||||||
am.mux.Lock()
|
am.mux.Lock()
|
||||||
|
@ -70,6 +70,36 @@ func TestAccountManager_AddAccount(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccountManager_GetAccountByUserOrAccountId(t *testing.T) {
|
||||||
|
manager, err := createManager(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userId := "test_user"
|
||||||
|
|
||||||
|
account, err := manager.GetAccountByUserOrAccountId(userId, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if account == nil {
|
||||||
|
t.Fatalf("expected to create an account for a user %s", userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountId := account.Id
|
||||||
|
|
||||||
|
_, err = manager.GetAccountByUserOrAccountId("", accountId)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected to get existing account after creation using userid, no account was found for a account %s", accountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = manager.GetAccountByUserOrAccountId("", "")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected an error when user and account IDs are empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccountManager_AccountExists(t *testing.T) {
|
func TestAccountManager_AccountExists(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
manager, err := createManager(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -258,7 +288,7 @@ func createManager(t *testing.T) (*AccountManager, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewManager(store, NewPeersUpdateManager()), nil
|
return NewManager(store, NewPeersUpdateManager(), nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createStore(t *testing.T) (Store, error) {
|
func createStore(t *testing.T) (Store, error) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/wiretrustee/wiretrustee/management/server/idp"
|
||||||
"github.com/wiretrustee/wiretrustee/util"
|
"github.com/wiretrustee/wiretrustee/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,6 +24,8 @@ type Config struct {
|
|||||||
Datadir string
|
Datadir string
|
||||||
|
|
||||||
HttpConfig *HttpServerConfig
|
HttpConfig *HttpServerConfig
|
||||||
|
|
||||||
|
IdpManagerConfig *idp.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// TURNConfig is a config of the TURNCredentialsManager
|
// TURNConfig is a config of the TURNCredentialsManager
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
//Peers is a handler that returns peers of the account
|
//Peers is a handler that returns peers of the account
|
||||||
type Peers struct {
|
type Peers struct {
|
||||||
accountManager *server.AccountManager
|
accountManager *server.AccountManager
|
||||||
|
authAudience string
|
||||||
}
|
}
|
||||||
|
|
||||||
//PeerResponse is a response sent to the client
|
//PeerResponse is a response sent to the client
|
||||||
@ -29,9 +30,10 @@ type PeerRequest struct {
|
|||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPeers(accountManager *server.AccountManager) *Peers {
|
func NewPeers(accountManager *server.AccountManager, authAudience string) *Peers {
|
||||||
return &Peers{
|
return &Peers{
|
||||||
accountManager: accountManager,
|
accountManager: accountManager,
|
||||||
|
authAudience: authAudience,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,8 +64,9 @@ func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseW
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||||
userId := extractUserIdFromRequestContext(r)
|
userId, accountId := extractUserAndAccountIdFromRequestContext(r, h.authAudience)
|
||||||
account, err := h.accountManager.GetOrCreateAccountByUser(userId)
|
//new user -> create a new account
|
||||||
|
account, err := h.accountManager.GetAccountByUserOrAccountId(userId, accountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed getting account of a user %s: %v", userId, err)
|
log.Errorf("failed getting account of a user %s: %v", userId, err)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
@ -102,9 +105,9 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
userId := extractUserIdFromRequestContext(r)
|
userId, accountId := extractUserAndAccountIdFromRequestContext(r, h.authAudience)
|
||||||
//new user -> create a new account
|
//new user -> create a new account
|
||||||
account, err := h.accountManager.GetOrCreateAccountByUser(userId)
|
account, err := h.accountManager.GetAccountByUserOrAccountId(userId, accountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed getting account of a user %s: %v", userId, err)
|
log.Errorf("failed getting account of a user %s: %v", userId, err)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
// SetupKeys is a handler that returns a list of setup keys of the account
|
// SetupKeys is a handler that returns a list of setup keys of the account
|
||||||
type SetupKeys struct {
|
type SetupKeys struct {
|
||||||
accountManager *server.AccountManager
|
accountManager *server.AccountManager
|
||||||
|
authAudience string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupKeyResponse is a response sent to the client
|
// SetupKeyResponse is a response sent to the client
|
||||||
@ -39,9 +40,10 @@ type SetupKeyRequest struct {
|
|||||||
Revoked bool
|
Revoked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSetupKeysHandler(accountManager *server.AccountManager) *SetupKeys {
|
func NewSetupKeysHandler(accountManager *server.AccountManager, authAudience string) *SetupKeys {
|
||||||
return &SetupKeys{
|
return &SetupKeys{
|
||||||
accountManager: accountManager,
|
accountManager: accountManager,
|
||||||
|
authAudience: authAudience,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,8 +120,8 @@ func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
||||||
userId := extractUserIdFromRequestContext(r)
|
userId, accountId := extractUserAndAccountIdFromRequestContext(r, h.authAudience)
|
||||||
account, err := h.accountManager.GetOrCreateAccountByUser(userId)
|
account, err := h.accountManager.GetAccountByUserOrAccountId(userId, accountId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed getting account of a user %s: %v", userId, err)
|
log.Errorf("failed getting account of a user %s: %v", userId, err)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
@ -147,9 +149,8 @@ func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
userId := extractUserIdFromRequestContext(r)
|
userId, accountId := extractUserAndAccountIdFromRequestContext(r, h.authAudience)
|
||||||
//new user -> create a new account
|
account, err := h.accountManager.GetAccountByUserOrAccountId(userId, accountId)
|
||||||
account, err := h.accountManager.GetOrCreateAccountByUser(userId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed getting account of a user %s: %v", userId, err)
|
log.Errorf("failed getting account of a user %s: %v", userId, err)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
@ -8,13 +8,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// extractUserIdFromRequestContext extracts accountId from the request context previously filled by the JWT token (after auth)
|
// extractUserAndAccountIdFromRequestContext extracts accountId from the request context previously filled by the JWT token (after auth)
|
||||||
func extractUserIdFromRequestContext(r *http.Request) string {
|
func extractUserAndAccountIdFromRequestContext(r *http.Request, authAudiance string) (userId, accountId string) {
|
||||||
token := r.Context().Value("user").(*jwt.Token)
|
token := r.Context().Value("user").(*jwt.Token)
|
||||||
claims := token.Claims.(jwt.MapClaims)
|
claims := token.Claims.(jwt.MapClaims)
|
||||||
|
|
||||||
//actually a user id but for now we have a 1 to 1 mapping.
|
userId = claims["sub"].(string)
|
||||||
return claims["sub"].(string)
|
accountIdInt, ok := claims[authAudiance+"wt_account_id"]
|
||||||
|
if ok {
|
||||||
|
accountId = accountIdInt.(string)
|
||||||
|
}
|
||||||
|
return userId, accountId
|
||||||
}
|
}
|
||||||
|
|
||||||
//writeJSONObject simply writes object to the HTTP reponse in JSON format
|
//writeJSONObject simply writes object to the HTTP reponse in JSON format
|
||||||
|
@ -73,8 +73,8 @@ func (s *Server) Start() error {
|
|||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.Use(jwtMiddleware.Handler, corsMiddleware.Handler)
|
r.Use(jwtMiddleware.Handler, corsMiddleware.Handler)
|
||||||
|
|
||||||
peersHandler := handler.NewPeers(s.accountManager)
|
peersHandler := handler.NewPeers(s.accountManager, s.config.AuthAudience)
|
||||||
keysHandler := handler.NewSetupKeysHandler(s.accountManager)
|
keysHandler := handler.NewSetupKeysHandler(s.accountManager, s.config.AuthAudience)
|
||||||
r.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
||||||
r.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).Methods("GET", "PUT", "DELETE", "OPTIONS")
|
r.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).Methods("GET", "PUT", "DELETE", "OPTIONS")
|
||||||
|
|
||||||
|
207
management/server/idp/auth0.go
Normal file
207
management/server/idp/auth0.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auth0Manager auth0 manager client instance
|
||||||
|
type Auth0Manager struct {
|
||||||
|
authIssuer string
|
||||||
|
httpClient ManagerHTTPClient
|
||||||
|
credentials ManagerCredentials
|
||||||
|
helper ManagerHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth0ClientConfig auth0 manager client configurations
|
||||||
|
type Auth0ClientConfig struct {
|
||||||
|
Audience string `json:"audiance"`
|
||||||
|
AuthIssuer string `json:"auth_issuer"`
|
||||||
|
ClientId string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth0Credentials auth0 authentication information
|
||||||
|
type Auth0Credentials struct {
|
||||||
|
clientConfig Auth0ClientConfig
|
||||||
|
helper ManagerHelper
|
||||||
|
httpClient ManagerHTTPClient
|
||||||
|
jwtToken JWTToken
|
||||||
|
mux sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuth0Manager creates a new instance of the Auth0Manager
|
||||||
|
func NewAuth0Manager(config Auth0ClientConfig) *Auth0Manager {
|
||||||
|
|
||||||
|
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
httpTransport.MaxIdleConns = 5
|
||||||
|
httpTransport.IdleConnTimeout = 30
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
Transport: httpTransport,
|
||||||
|
}
|
||||||
|
|
||||||
|
helper := JsonParser{}
|
||||||
|
|
||||||
|
credentials := &Auth0Credentials{
|
||||||
|
clientConfig: config,
|
||||||
|
httpClient: httpClient,
|
||||||
|
helper: helper,
|
||||||
|
}
|
||||||
|
return &Auth0Manager{
|
||||||
|
authIssuer: config.AuthIssuer,
|
||||||
|
credentials: credentials,
|
||||||
|
httpClient: httpClient,
|
||||||
|
helper: helper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwtStillValid returns true if the token still valid and have enough time to be used and get a response from Auth0
|
||||||
|
func (c *Auth0Credentials) jwtStillValid() bool {
|
||||||
|
return !c.jwtToken.expiresInTime.IsZero() && time.Now().Add(5*time.Second).Before(c.jwtToken.expiresInTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestJWTToken performs request to get jwt token
|
||||||
|
func (c *Auth0Credentials) requestJWTToken() (*http.Response, error) {
|
||||||
|
var res *http.Response
|
||||||
|
url := c.clientConfig.AuthIssuer + "/oauth/token"
|
||||||
|
|
||||||
|
p, err := c.helper.Marshal(c.clientConfig)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
payload := strings.NewReader(string(p))
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, payload)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("content-type", "application/json")
|
||||||
|
|
||||||
|
res, err = c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return res, fmt.Errorf("unable to get token, statusCode %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseRequestJWTResponse parses jwt raw response body and extracts token and expires in seconds
|
||||||
|
func (c *Auth0Credentials) parseRequestJWTResponse(rawBody io.ReadCloser) (JWTToken, error) {
|
||||||
|
jwtToken := JWTToken{}
|
||||||
|
body, err := ioutil.ReadAll(rawBody)
|
||||||
|
if err != nil {
|
||||||
|
return jwtToken, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.helper.Unmarshal(body, &jwtToken)
|
||||||
|
if err != nil {
|
||||||
|
return jwtToken, err
|
||||||
|
}
|
||||||
|
if jwtToken.ExpiresIn == 0 && jwtToken.AccessToken == "" {
|
||||||
|
return jwtToken, fmt.Errorf("error while reading response body, expires_in: %d and access_token: %s", jwtToken.ExpiresIn, jwtToken.AccessToken)
|
||||||
|
}
|
||||||
|
data, err := jwt.DecodeSegment(strings.Split(jwtToken.AccessToken, ".")[1])
|
||||||
|
if err != nil {
|
||||||
|
return jwtToken, err
|
||||||
|
}
|
||||||
|
// Exp maps into exp from jwt token
|
||||||
|
var IssuedAt struct{ Exp int64 }
|
||||||
|
err = json.Unmarshal(data, &IssuedAt)
|
||||||
|
if err != nil {
|
||||||
|
return jwtToken, err
|
||||||
|
}
|
||||||
|
jwtToken.expiresInTime = time.Unix(IssuedAt.Exp, 0)
|
||||||
|
|
||||||
|
return jwtToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate retrieves access token to use the Auth0 Management API
|
||||||
|
func (c *Auth0Credentials) Authenticate() (JWTToken, error) {
|
||||||
|
c.mux.Lock()
|
||||||
|
defer c.mux.Unlock()
|
||||||
|
|
||||||
|
// If jwtToken has an expires time and we have enough time to do a request return immediately
|
||||||
|
if c.jwtStillValid() {
|
||||||
|
return c.jwtToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.requestJWTToken()
|
||||||
|
if err != nil {
|
||||||
|
return c.jwtToken, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while closing get jwt token response body: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
jwtToken, err := c.parseRequestJWTResponse(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return c.jwtToken, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.jwtToken = jwtToken
|
||||||
|
|
||||||
|
return c.jwtToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserAppMetadata updates user app metadata based on userId and metadata map
|
||||||
|
func (am *Auth0Manager) UpdateUserAppMetadata(userId string, appMetadata AppMetadata) error {
|
||||||
|
|
||||||
|
jwtToken, err := am.credentials.Authenticate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := am.authIssuer + "/api/v2/users/" + userId
|
||||||
|
|
||||||
|
data, err := am.helper.Marshal(appMetadata)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadString := fmt.Sprintf("{\"app_metadata\": %s}", string(data))
|
||||||
|
|
||||||
|
payload := strings.NewReader(payloadString)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PATCH", url, payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
||||||
|
req.Header.Add("content-type", "application/json")
|
||||||
|
|
||||||
|
res, err := am.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while closing update user app metadata response body: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("unable to update the appMetadata, statusCode %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
403
management/server/idp/auth0_test.go
Normal file
403
management/server/idp/auth0_test.go
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockHTTPClient struct {
|
||||||
|
code int
|
||||||
|
resBody string
|
||||||
|
reqBody string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
body, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err == nil {
|
||||||
|
c.reqBody = string(body)
|
||||||
|
}
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: c.code,
|
||||||
|
Body: ioutil.NopCloser(strings.NewReader(c.resBody)),
|
||||||
|
}, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockJsonParser struct {
|
||||||
|
jsonParser JsonParser
|
||||||
|
marshalErrorString string
|
||||||
|
unmarshalErrorString string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockJsonParser) Marshal(v interface{}) ([]byte, error) {
|
||||||
|
if m.marshalErrorString != "" {
|
||||||
|
return nil, fmt.Errorf(m.marshalErrorString)
|
||||||
|
}
|
||||||
|
return m.jsonParser.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockJsonParser) Unmarshal(data []byte, v interface{}) error {
|
||||||
|
if m.unmarshalErrorString != "" {
|
||||||
|
return fmt.Errorf(m.unmarshalErrorString)
|
||||||
|
}
|
||||||
|
return m.jsonParser.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockAuth0Credentials struct {
|
||||||
|
jwtToken JWTToken
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockAuth0Credentials) Authenticate() (JWTToken, error) {
|
||||||
|
return mc.jwtToken, mc.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestJWT(t *testing.T, expInt int) string {
|
||||||
|
now := time.Now()
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
|
"iat": now.Unix(),
|
||||||
|
"exp": now.Add(time.Duration(expInt) * time.Second).Unix(),
|
||||||
|
})
|
||||||
|
var hmacSampleSecret []byte
|
||||||
|
tokenString, err := token.SignedString(hmacSampleSecret)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return tokenString
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuth0_RequestJWTToken(t *testing.T) {
|
||||||
|
|
||||||
|
type requestJWTTokenTest struct {
|
||||||
|
name string
|
||||||
|
inputCode int
|
||||||
|
inputResBody string
|
||||||
|
helper ManagerHelper
|
||||||
|
expectedFuncExitErrDiff error
|
||||||
|
expectedCode int
|
||||||
|
expectedToken string
|
||||||
|
}
|
||||||
|
exp := 5
|
||||||
|
token := newTestJWT(t, exp)
|
||||||
|
|
||||||
|
requestJWTTokenTesttCase1 := requestJWTTokenTest{
|
||||||
|
name: "Get Good JWT Response",
|
||||||
|
inputCode: 200,
|
||||||
|
inputResBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
||||||
|
helper: JsonParser{},
|
||||||
|
expectedCode: 200,
|
||||||
|
expectedToken: token,
|
||||||
|
}
|
||||||
|
requestJWTTokenTestCase2 := requestJWTTokenTest{
|
||||||
|
name: "Request Bad Status Code",
|
||||||
|
inputCode: 400,
|
||||||
|
inputResBody: "{}",
|
||||||
|
helper: JsonParser{},
|
||||||
|
expectedFuncExitErrDiff: fmt.Errorf("unable to get token, statusCode 400"),
|
||||||
|
expectedCode: 200,
|
||||||
|
expectedToken: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range []requestJWTTokenTest{requestJWTTokenTesttCase1, requestJWTTokenTestCase2} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
jwtReqClient := mockHTTPClient{
|
||||||
|
resBody: testCase.inputResBody,
|
||||||
|
code: testCase.inputCode,
|
||||||
|
}
|
||||||
|
config := Auth0ClientConfig{}
|
||||||
|
|
||||||
|
creds := Auth0Credentials{
|
||||||
|
clientConfig: config,
|
||||||
|
httpClient: &jwtReqClient,
|
||||||
|
helper: testCase.helper,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := creds.requestJWTToken()
|
||||||
|
if err != nil {
|
||||||
|
if testCase.expectedFuncExitErrDiff != nil {
|
||||||
|
assert.EqualError(t, err, testCase.expectedFuncExitErrDiff.Error(), "errors should be the same")
|
||||||
|
} else {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
assert.NoError(t, err, "unable to read the response body")
|
||||||
|
|
||||||
|
jwtToken := JWTToken{}
|
||||||
|
err = json.Unmarshal(body, &jwtToken)
|
||||||
|
assert.NoError(t, err, "unable to parse the json input")
|
||||||
|
|
||||||
|
assert.Equalf(t, testCase.expectedToken, jwtToken.AccessToken, "two tokens should be the same")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuth0_ParseRequestJWTResponse(t *testing.T) {
|
||||||
|
type parseRequestJWTResponseTest struct {
|
||||||
|
name string
|
||||||
|
inputResBody string
|
||||||
|
helper ManagerHelper
|
||||||
|
expectedToken string
|
||||||
|
expectedExpiresIn int
|
||||||
|
assertErrFunc func(t assert.TestingT, err error, msgAndArgs ...interface{}) bool
|
||||||
|
assertErrFuncMessage string
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := 100
|
||||||
|
token := newTestJWT(t, exp)
|
||||||
|
|
||||||
|
parseRequestJWTResponseTestCase1 := parseRequestJWTResponseTest{
|
||||||
|
name: "Parse Good JWT Body",
|
||||||
|
inputResBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
||||||
|
helper: JsonParser{},
|
||||||
|
expectedToken: token,
|
||||||
|
expectedExpiresIn: exp,
|
||||||
|
assertErrFunc: assert.NoError,
|
||||||
|
assertErrFuncMessage: "no error was expected",
|
||||||
|
}
|
||||||
|
parseRequestJWTResponseTestCase2 := parseRequestJWTResponseTest{
|
||||||
|
name: "Parse Bad json JWT Body",
|
||||||
|
inputResBody: "",
|
||||||
|
helper: JsonParser{},
|
||||||
|
expectedToken: "",
|
||||||
|
expectedExpiresIn: 0,
|
||||||
|
assertErrFunc: assert.Error,
|
||||||
|
assertErrFuncMessage: "json error was expected",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range []parseRequestJWTResponseTest{parseRequestJWTResponseTestCase1, parseRequestJWTResponseTestCase2} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
rawBody := ioutil.NopCloser(strings.NewReader(testCase.inputResBody))
|
||||||
|
|
||||||
|
config := Auth0ClientConfig{}
|
||||||
|
|
||||||
|
creds := Auth0Credentials{
|
||||||
|
clientConfig: config,
|
||||||
|
helper: testCase.helper,
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtToken, err := creds.parseRequestJWTResponse(rawBody)
|
||||||
|
testCase.assertErrFunc(t, err, testCase.assertErrFuncMessage)
|
||||||
|
|
||||||
|
assert.Equalf(t, testCase.expectedToken, jwtToken.AccessToken, "two tokens should be the same")
|
||||||
|
assert.Equalf(t, testCase.expectedExpiresIn, jwtToken.ExpiresIn, "the two expire times should be the same")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuth0_JwtStillValid(t *testing.T) {
|
||||||
|
|
||||||
|
type jwtStillValidTest struct {
|
||||||
|
name string
|
||||||
|
inputTime time.Time
|
||||||
|
expectedResult bool
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
jwtStillValidTestCase1 := jwtStillValidTest{
|
||||||
|
name: "JWT still valid",
|
||||||
|
inputTime: time.Now().Add(10 * time.Second),
|
||||||
|
expectedResult: true,
|
||||||
|
message: "should be true",
|
||||||
|
}
|
||||||
|
jwtStillValidTestCase2 := jwtStillValidTest{
|
||||||
|
name: "JWT is invalid",
|
||||||
|
inputTime: time.Now(),
|
||||||
|
expectedResult: false,
|
||||||
|
message: "should be false",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range []jwtStillValidTest{jwtStillValidTestCase1, jwtStillValidTestCase2} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
config := Auth0ClientConfig{}
|
||||||
|
|
||||||
|
creds := Auth0Credentials{
|
||||||
|
clientConfig: config,
|
||||||
|
}
|
||||||
|
creds.jwtToken.expiresInTime = testCase.inputTime
|
||||||
|
|
||||||
|
assert.Equalf(t, testCase.expectedResult, creds.jwtStillValid(), testCase.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuth0_Authenticate(t *testing.T) {
|
||||||
|
type authenticateTest struct {
|
||||||
|
name string
|
||||||
|
inputCode int
|
||||||
|
inputResBody string
|
||||||
|
inputExpireToken time.Time
|
||||||
|
helper ManagerHelper
|
||||||
|
expectedFuncExitErrDiff error
|
||||||
|
expectedCode int
|
||||||
|
expectedToken string
|
||||||
|
}
|
||||||
|
exp := 5
|
||||||
|
token := newTestJWT(t, exp)
|
||||||
|
|
||||||
|
authenticateTestCase1 := authenticateTest{
|
||||||
|
name: "Get Cached token",
|
||||||
|
inputExpireToken: time.Now().Add(30 * time.Second),
|
||||||
|
helper: JsonParser{},
|
||||||
|
//expectedFuncExitErrDiff: fmt.Errorf("unable to get token, statusCode 400"),
|
||||||
|
expectedCode: 200,
|
||||||
|
expectedToken: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticateTestCase2 := authenticateTest{
|
||||||
|
name: "Get Good JWT Response",
|
||||||
|
inputCode: 200,
|
||||||
|
inputResBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
||||||
|
helper: JsonParser{},
|
||||||
|
expectedCode: 200,
|
||||||
|
expectedToken: token,
|
||||||
|
}
|
||||||
|
authenticateTestCase3 := authenticateTest{
|
||||||
|
name: "Get Bad Status Code",
|
||||||
|
inputCode: 400,
|
||||||
|
inputResBody: "{}",
|
||||||
|
helper: JsonParser{},
|
||||||
|
expectedFuncExitErrDiff: fmt.Errorf("unable to get token, statusCode 400"),
|
||||||
|
expectedCode: 200,
|
||||||
|
expectedToken: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range []authenticateTest{authenticateTestCase1, authenticateTestCase2, authenticateTestCase3} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
jwtReqClient := mockHTTPClient{
|
||||||
|
resBody: testCase.inputResBody,
|
||||||
|
code: testCase.inputCode,
|
||||||
|
}
|
||||||
|
config := Auth0ClientConfig{}
|
||||||
|
|
||||||
|
creds := Auth0Credentials{
|
||||||
|
clientConfig: config,
|
||||||
|
httpClient: &jwtReqClient,
|
||||||
|
helper: testCase.helper,
|
||||||
|
}
|
||||||
|
|
||||||
|
creds.jwtToken.expiresInTime = testCase.inputExpireToken
|
||||||
|
|
||||||
|
_, err := creds.Authenticate()
|
||||||
|
if err != nil {
|
||||||
|
if testCase.expectedFuncExitErrDiff != nil {
|
||||||
|
assert.EqualError(t, err, testCase.expectedFuncExitErrDiff.Error(), "errors should be the same")
|
||||||
|
} else {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equalf(t, testCase.expectedToken, creds.jwtToken.AccessToken, "two tokens should be the same")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuth0_UpdateUserAppMetadata(t *testing.T) {
|
||||||
|
|
||||||
|
type updateUserAppMetadataTest struct {
|
||||||
|
name string
|
||||||
|
inputReqBody string
|
||||||
|
expectedReqBody string
|
||||||
|
appMetadata AppMetadata
|
||||||
|
statusCode int
|
||||||
|
helper ManagerHelper
|
||||||
|
managerCreds ManagerCredentials
|
||||||
|
assertErrFunc func(t assert.TestingT, err error, msgAndArgs ...interface{}) bool
|
||||||
|
assertErrFuncMessage string
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := 15
|
||||||
|
token := newTestJWT(t, exp)
|
||||||
|
appMetadata := AppMetadata{WTAccountId: "ok"}
|
||||||
|
|
||||||
|
updateUserAppMetadataTestCase1 := updateUserAppMetadataTest{
|
||||||
|
name: "Bad Authentication",
|
||||||
|
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
||||||
|
expectedReqBody: "",
|
||||||
|
appMetadata: appMetadata,
|
||||||
|
statusCode: 400,
|
||||||
|
helper: JsonParser{},
|
||||||
|
managerCreds: &mockAuth0Credentials{
|
||||||
|
jwtToken: JWTToken{},
|
||||||
|
err: fmt.Errorf("error"),
|
||||||
|
},
|
||||||
|
assertErrFunc: assert.Error,
|
||||||
|
assertErrFuncMessage: "should return error",
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserAppMetadataTestCase2 := updateUserAppMetadataTest{
|
||||||
|
name: "Bad Status Code",
|
||||||
|
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
||||||
|
expectedReqBody: fmt.Sprintf("{\"app_metadata\": {\"wt_account_id\":\"%s\"}}", appMetadata.WTAccountId),
|
||||||
|
appMetadata: appMetadata,
|
||||||
|
statusCode: 400,
|
||||||
|
helper: JsonParser{},
|
||||||
|
managerCreds: &mockAuth0Credentials{
|
||||||
|
jwtToken: JWTToken{},
|
||||||
|
},
|
||||||
|
assertErrFunc: assert.Error,
|
||||||
|
assertErrFuncMessage: "should return error",
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserAppMetadataTestCase3 := updateUserAppMetadataTest{
|
||||||
|
name: "Bad Response Parsing",
|
||||||
|
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
||||||
|
statusCode: 400,
|
||||||
|
helper: &mockJsonParser{marshalErrorString: "error"},
|
||||||
|
assertErrFunc: assert.Error,
|
||||||
|
assertErrFuncMessage: "should return error",
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserAppMetadataTestCase4 := updateUserAppMetadataTest{
|
||||||
|
name: "Good request",
|
||||||
|
inputReqBody: fmt.Sprintf("{\"access_token\":\"%s\",\"scope\":\"read:users\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", token, exp),
|
||||||
|
expectedReqBody: fmt.Sprintf("{\"app_metadata\": {\"wt_account_id\":\"%s\"}}", appMetadata.WTAccountId),
|
||||||
|
appMetadata: appMetadata,
|
||||||
|
statusCode: 200,
|
||||||
|
helper: JsonParser{},
|
||||||
|
assertErrFunc: assert.NoError,
|
||||||
|
assertErrFuncMessage: "shouldn't return error",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range []updateUserAppMetadataTest{updateUserAppMetadataTestCase1, updateUserAppMetadataTestCase2, updateUserAppMetadataTestCase3, updateUserAppMetadataTestCase4} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
jwtReqClient := mockHTTPClient{
|
||||||
|
resBody: testCase.inputReqBody,
|
||||||
|
code: testCase.statusCode,
|
||||||
|
}
|
||||||
|
config := Auth0ClientConfig{}
|
||||||
|
|
||||||
|
var creds ManagerCredentials
|
||||||
|
if testCase.managerCreds != nil {
|
||||||
|
creds = testCase.managerCreds
|
||||||
|
} else {
|
||||||
|
creds = &Auth0Credentials{
|
||||||
|
clientConfig: config,
|
||||||
|
httpClient: &jwtReqClient,
|
||||||
|
helper: testCase.helper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := Auth0Manager{
|
||||||
|
httpClient: &jwtReqClient,
|
||||||
|
credentials: creds,
|
||||||
|
helper: testCase.helper,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := manager.UpdateUserAppMetadata("1", testCase.appMetadata)
|
||||||
|
testCase.assertErrFunc(t, err, testCase.assertErrFuncMessage)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.expectedReqBody, jwtReqClient.reqBody, "request body should match")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
63
management/server/idp/idp.go
Normal file
63
management/server/idp/idp.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager idp manager interface
|
||||||
|
type Manager interface {
|
||||||
|
UpdateUserAppMetadata(userId string, appMetadata AppMetadata) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config an idp configuration struct to be loaded from management server's config file
|
||||||
|
type Config struct {
|
||||||
|
ManagerType string
|
||||||
|
Auth0ClientCredentials Auth0ClientConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManagerCredentials interface that authenticates using the credential of each type of idp
|
||||||
|
type ManagerCredentials interface {
|
||||||
|
Authenticate() (JWTToken, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManagerHTTPClient http client interface for API calls
|
||||||
|
type ManagerHTTPClient interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManagerHelper helper
|
||||||
|
type ManagerHelper interface {
|
||||||
|
Marshal(v interface{}) ([]byte, error)
|
||||||
|
Unmarshal(data []byte, v interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppMetadata user app metadata to associate with a profile
|
||||||
|
type AppMetadata struct {
|
||||||
|
// Wiretrustee account id to update in the IDP
|
||||||
|
// maps to wt_account_id when json.marshal
|
||||||
|
WTAccountId string `json:"wt_account_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTToken a JWT object that holds information of a token
|
||||||
|
type JWTToken struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
expiresInTime time.Time
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager returns a new idp manager based on the configuration that it receives
|
||||||
|
func NewManager(config Config) (Manager, error) {
|
||||||
|
switch strings.ToLower(config.ManagerType) {
|
||||||
|
case "none", "":
|
||||||
|
return nil, nil
|
||||||
|
case "auth0":
|
||||||
|
return NewAuth0Manager(config.Auth0ClientCredentials), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid manager type: %s", config.ManagerType)
|
||||||
|
}
|
||||||
|
}
|
13
management/server/idp/util.go
Normal file
13
management/server/idp/util.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package idp
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type JsonParser struct{}
|
||||||
|
|
||||||
|
func (JsonParser) Marshal(v interface{}) ([]byte, error) {
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (JsonParser) Unmarshal(data []byte, v interface{}) error {
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
@ -313,7 +313,7 @@ func startManagement(port int, config *Config) (*grpc.Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
peersUpdateManager := NewPeersUpdateManager()
|
peersUpdateManager := NewPeersUpdateManager()
|
||||||
accountManager := NewManager(store, peersUpdateManager)
|
accountManager := NewManager(store, peersUpdateManager, nil)
|
||||||
turnManager := NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
turnManager := NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
mgmtServer, err := NewServer(config, accountManager, peersUpdateManager, turnManager)
|
mgmtServer, err := NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -496,7 +496,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
|
|||||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||||
}
|
}
|
||||||
peersUpdateManager := server.NewPeersUpdateManager()
|
peersUpdateManager := server.NewPeersUpdateManager()
|
||||||
accountManager := server.NewManager(store, peersUpdateManager)
|
accountManager := server.NewManager(store, peersUpdateManager, nil)
|
||||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
10
management/server/testdata/management.json
vendored
10
management/server/testdata/management.json
vendored
@ -33,5 +33,15 @@
|
|||||||
"AuthIssuer": "<PASTE YOUR AUTH0 ISSUER HERE>,",
|
"AuthIssuer": "<PASTE YOUR AUTH0 ISSUER HERE>,",
|
||||||
"AuthAudience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
"AuthAudience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
||||||
"AuthKeysLocation": "<PASTE YOUR AUTH0 PUBLIC JWT KEYS LOCATION HERE>"
|
"AuthKeysLocation": "<PASTE YOUR AUTH0 PUBLIC JWT KEYS LOCATION HERE>"
|
||||||
|
},
|
||||||
|
"IdpManagerConfig": {
|
||||||
|
"Manager": "<none|auth0>",
|
||||||
|
"Auth0ClientCredentials": {
|
||||||
|
"Audience": "<PASTE YOUR AUTH0 AUDIENCE HERE>",
|
||||||
|
"AuthIssuer": "<PASTE YOUR AUTH0 Auth Issuer HERE>",
|
||||||
|
"ClientId": "<PASTE YOUR AUTH0 Application Client ID HERE>",
|
||||||
|
"ClientSecret": "<PASTE YOUR AUTH0 Application Client Secret HERE>",
|
||||||
|
"GrantType": "client_credentials"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user