Compare commits

...

10 Commits

Author SHA1 Message Date
Maycon Santos
5f41e2bd13 Merge branch 'main' into debug-google-workspace 2024-02-19 15:26:58 +01:00
Maycon Santos
cb3408a10b Allow adding 3 nameserver addresses (#1588) 2024-02-19 14:29:20 +01:00
Viktor Liu
0afd738509 Make sure the iOS dialer does not get overwritten (#1585)
* Make sure our iOS dialer does not get overwritten

* set dial timeout for both clients on ios

---------

Co-authored-by: Pascal Fischer <pascal@netbird.io>
2024-02-16 14:37:47 +01:00
bcmmbaga
e3d038da8a debug google workspace request 2024-02-16 13:10:37 +03:00
Maycon Santos
cf87f1e702 Fix/prevent returning error from external cache (#1576)
* Prevent returning error from external cache query

* link comment

* fix spell and remove unnecessary return
2024-02-13 13:10:17 +01:00
Maycon Santos
e890fdae54 Return error when peer is not valid (#1573)
Fix count with invalid peers
2024-02-13 10:59:31 +01:00
Maycon Santos
dd14db6478 Properly handle cache error and return userdata (#1571) 2024-02-12 21:54:16 +01:00
Maycon Santos
88747e3e01 Add an extra server reflexive candidate with WG port (#1549)
sends an extra server reflexive candidate to the remote peer with our related port (usually the Wireguard port)
this is useful when a network has an existing port forwarding rule for the Wireguard port and the local peer and avoids creating a 1:1 NAT on the local network.
2024-02-08 16:50:37 +01:00
Yury Gargay
fb30931365 Expose trusted proxy list and counter configuration for realip middleware (#1535) 2024-02-08 14:40:40 +01:00
Maycon Santos
a7547b9990 Get cache from external cache when refresh fails (#1537)
In some cases, when the refresh cache fails, we should try to get the cache from the external cache obj.

This may happen if the IDP is not responsive between storing metadata and refreshing the cache
2024-02-07 16:14:30 +01:00
14 changed files with 152 additions and 42 deletions

View File

@@ -46,24 +46,32 @@ func (u *upstreamResolverIOS) exchange(ctx context.Context, upstream string, r *
if err != nil { if err != nil {
log.Errorf("error while parsing upstream host: %s", err) log.Errorf("error while parsing upstream host: %s", err)
} }
timeout := upstreamTimeout
if deadline, ok := ctx.Deadline(); ok {
timeout = time.Until(deadline)
}
client.DialTimeout = timeout
upstreamIP := net.ParseIP(upstreamHost) upstreamIP := net.ParseIP(upstreamHost)
if u.lNet.Contains(upstreamIP) || net.IP.IsPrivate(upstreamIP) { if u.lNet.Contains(upstreamIP) || net.IP.IsPrivate(upstreamIP) {
log.Debugf("using private client to query upstream: %s", upstream) log.Debugf("using private client to query upstream: %s", upstream)
client = u.getClientPrivate() client = u.getClientPrivate(timeout)
} }
return client.ExchangeContext(ctx, r, upstream) // Cannot use client.ExchangeContext because it overwrites our Dialer
return client.Exchange(r, upstream)
} }
// getClientPrivate returns a new DNS client bound to the local IP address of the Netbird interface // getClientPrivate returns a new DNS client bound to the local IP address of the Netbird interface
// This method is needed for iOS // This method is needed for iOS
func (u *upstreamResolverIOS) getClientPrivate() *dns.Client { func (u *upstreamResolverIOS) getClientPrivate(dialTimeout time.Duration) *dns.Client {
dialer := &net.Dialer{ dialer := &net.Dialer{
LocalAddr: &net.UDPAddr{ LocalAddr: &net.UDPAddr{
IP: u.lIP, IP: u.lIP,
Port: 0, // Let the OS pick a free port Port: 0, // Let the OS pick a free port
}, },
Timeout: upstreamTimeout, Timeout: dialTimeout,
Control: func(network, address string, c syscall.RawConn) error { Control: func(network, address string, c syscall.RawConn) error {
var operr error var operr error
fn := func(s uintptr) { fn := func(s uintptr) {

View File

@@ -130,8 +130,9 @@ type Conn struct {
remoteModeCh chan ModeMessage remoteModeCh chan ModeMessage
meta meta meta meta
adapter iface.TunAdapter adapter iface.TunAdapter
iFaceDiscover stdnet.ExternalIFaceDiscover iFaceDiscover stdnet.ExternalIFaceDiscover
sentExtraSrflx bool
} }
// meta holds meta information about a connection // meta holds meta information about a connection
@@ -464,6 +465,8 @@ func (conn *Conn) cleanup() error {
conn.mu.Lock() conn.mu.Lock()
defer conn.mu.Unlock() defer conn.mu.Unlock()
conn.sentExtraSrflx = false
var err1, err2, err3 error var err1, err2, err3 error
if conn.agent != nil { if conn.agent != nil {
err1 = conn.agent.Close() err1 = conn.agent.Close()
@@ -557,6 +560,30 @@ func (conn *Conn) onICECandidate(candidate ice.Candidate) {
if err != nil { if err != nil {
log.Errorf("failed signaling candidate to the remote peer %s %s", conn.config.Key, err) log.Errorf("failed signaling candidate to the remote peer %s %s", conn.config.Key, err)
} }
// sends an extra server reflexive candidate to the remote peer with our related port (usually the wireguard port)
// this is useful when network has an existing port forwarding rule for the wireguard port and this peer
if !conn.sentExtraSrflx && candidate.Type() == ice.CandidateTypeServerReflexive && candidate.Port() != candidate.RelatedAddress().Port {
relatedAdd := candidate.RelatedAddress()
extraSrflx, err := ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
Network: candidate.NetworkType().String(),
Address: candidate.Address(),
Port: relatedAdd.Port,
Component: candidate.Component(),
RelAddr: relatedAdd.Address,
RelPort: relatedAdd.Port,
})
if err != nil {
log.Errorf("failed creating extra server reflexive candidate %s", err)
return
}
err = conn.signalCandidate(extraSrflx)
if err != nil {
log.Errorf("failed signaling the extra server reflexive candidate to the remote peer %s: %s", conn.config.Key, err)
return
}
conn.sentExtraSrflx = true
}
}() }()
} }
} }

2
go.mod
View File

@@ -171,3 +171,5 @@ replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-202
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20240105182236-6c340dd55aed replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20240105182236-6c340dd55aed
replace github.com/cloudflare/circl => github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6 replace github.com/cloudflare/circl => github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
replace github.com/grpc-ecosystem/go-grpc-middleware/v2 => github.com/surik/go-grpc-middleware/v2 v2.0.0-20240206110057-98a38fc1f86f

4
go.sum
View File

@@ -286,8 +286,6 @@ github.com/gopherjs/gopherjs v0.0.0-20220410123724-9e86199038b0 h1:fWY+zXdWhvWnd
github.com/gopherjs/gopherjs v0.0.0-20220410123724-9e86199038b0/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gopherjs/gopherjs v0.0.0-20220410123724-9e86199038b0/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240202184442-37827591b26c h1:Kvw2BIua5WGDnknpnODn9K74PYWLhhqt8G3l0chyzEI=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240202184442-37827591b26c/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
@@ -519,6 +517,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/surik/go-grpc-middleware/v2 v2.0.0-20240206110057-98a38fc1f86f h1:J+egXEDkpg/vOYYzPO5IwF8OufGb7g+KcwEF1AWIzhQ=
github.com/surik/go-grpc-middleware/v2 v2.0.0-20240206110057-98a38fc1f86f/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0= github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0=
github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ= github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=

View File

@@ -15,6 +15,7 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"slices"
"strings" "strings"
"time" "time"
@@ -170,21 +171,31 @@ var (
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig) turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
trustedPeers := config.TrustedHTTPProxies trustedPeers := config.ReverseProxy.TrustedPeers
if len(trustedPeers) == 0 { defaultTrustedPeers := []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0")}
trustedPeers = []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")} if len(trustedPeers) == 0 || slices.Equal[[]netip.Prefix](trustedPeers, defaultTrustedPeers) {
log.Warn("TrustedPeers are configured to default value '0.0.0.0/0', '::/0'. This allows connection IP spoofing.")
trustedPeers = defaultTrustedPeers
}
trustedHTTPProxies := config.ReverseProxy.TrustedHTTPProxies
trustedProxiesCount := config.ReverseProxy.TrustedHTTPProxiesCount
if len(trustedHTTPProxies) > 0 && trustedProxiesCount > 0 {
log.Warn("TrustedHTTPProxies and TrustedHTTPProxiesCount both are configured. " +
"This is not recommended way to extract X-Forwarded-For. Consider using one of these options.")
}
realipOpts := realip.Opts{
TrustedPeers: trustedPeers,
TrustedProxies: trustedHTTPProxies,
TrustedProxiesCount: trustedProxiesCount,
Headers: []string{realip.XForwardedFor, realip.XRealIp},
} }
headers := []string{realip.XForwardedFor, realip.XRealIp}
gRPCOpts := []grpc.ServerOption{ gRPCOpts := []grpc.ServerOption{
grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveEnforcementPolicy(kaep),
grpc.KeepaliveParams(kasp), grpc.KeepaliveParams(kasp),
grpc.ChainUnaryInterceptor( grpc.ChainUnaryInterceptor(realip.UnaryServerInterceptorOpts(realipOpts)),
realip.UnaryServerInterceptor(trustedPeers, headers), grpc.ChainStreamInterceptor(realip.StreamServerInterceptorOpts(realipOpts)),
),
grpc.ChainStreamInterceptor(
realip.StreamServerInterceptor(trustedPeers, headers),
),
} }
var certManager *autocert.Manager var certManager *autocert.Manager
var tlsConfig *tls.Config var tlsConfig *tls.Config
tlsEnabled := false tlsEnabled := false

View File

@@ -1223,7 +1223,21 @@ func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Accou
} }
} }
return nil, nil //nolint:nilnil // add extra check on external cache manager. We may get to this point when the user is not yet findable in IDP,
// or it didn't have its metadata updated with am.addAccountIDToIDPAppMeta
user, err := account.FindUser(userID)
if err != nil {
log.Errorf("failed finding user %s in account %s", userID, account.Id)
return nil, err
}
key := user.IntegrationReference.CacheKey(account.Id, userID)
ud, err := am.externalCacheManager.Get(am.ctx, key)
if err != nil {
log.Debugf("failed to get externalCache for key: %s, error: %s", key, err)
}
return ud, nil
} }
func (am *DefaultAccountManager) refreshCache(accountID string) ([]*idp.UserData, error) { func (am *DefaultAccountManager) refreshCache(accountID string) ([]*idp.UserData, error) {

View File

@@ -41,8 +41,6 @@ type Config struct {
HttpConfig *HttpServerConfig HttpConfig *HttpServerConfig
TrustedHTTPProxies []netip.Prefix
IdpManagerConfig *idp.Config IdpManagerConfig *idp.Config
DeviceAuthorizationFlow *DeviceAuthorizationFlow DeviceAuthorizationFlow *DeviceAuthorizationFlow
@@ -50,6 +48,8 @@ type Config struct {
PKCEAuthorizationFlow *PKCEAuthorizationFlow PKCEAuthorizationFlow *PKCEAuthorizationFlow
StoreConfig StoreConfig StoreConfig StoreConfig
ReverseProxy ReverseProxy
} }
// GetAuthAudiences returns the audience from the http config and device authorization flow config // GetAuthAudiences returns the audience from the http config and device authorization flow config
@@ -146,6 +146,27 @@ type StoreConfig struct {
Engine StoreEngine Engine StoreEngine
} }
// ReverseProxy contains reverse proxy configuration in front of management.
type ReverseProxy struct {
// TrustedHTTPProxies represents a list of trusted HTTP proxies by their IP prefixes.
// When extracting the real IP address from request headers, the middleware will verify
// if the peer's address falls within one of these trusted IP prefixes.
TrustedHTTPProxies []netip.Prefix
// TrustedHTTPProxiesCount specifies the count of trusted HTTP proxies between the internet
// and the server. When using the trusted proxy count method to extract the real IP address,
// the middleware will search the X-Forwarded-For IP list from the rightmost by this count
// minus one.
TrustedHTTPProxiesCount uint
// TrustedPeers represents a list of trusted peers by their IP prefixes.
// These peers are considered trustworthy by the gRPC server operator,
// and the middleware will attempt to extract the real IP address from
// request headers if the peer's address falls within one of these
// trusted IP prefixes.
TrustedPeers []netip.Prefix
}
// validateURL validates input http url // validateURL validates input http url
func validateURL(httpURL string) bool { func validateURL(httpURL string) bool {
_, err := url.ParseRequestURI(httpURL) _, err := url.ParseRequestURI(httpURL)

View File

@@ -115,6 +115,13 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
if err != nil { if err != nil {
return err return err
} }
for _, peerID := range newGroup.Peers {
if account.Peers[peerID] == nil {
return status.Errorf(status.InvalidArgument, "peer with ID \"%s\" not found", peerID)
}
}
oldGroup, exists := account.Groups[newGroup.ID] oldGroup, exists := account.Groups[newGroup.ID]
account.Groups[newGroup.ID] = newGroup account.Groups[newGroup.ID] = newGroup

View File

@@ -904,7 +904,7 @@ components:
nameservers: nameservers:
description: Nameserver list description: Nameserver list
minLength: 1 minLength: 1
maxLength: 2 maxLength: 3
type: array type: array
items: items:
$ref: '#/components/schemas/Nameserver' $ref: '#/components/schemas/Nameserver'

View File

@@ -240,10 +240,9 @@ func (h *GroupsHandler) GetGroup(w http.ResponseWriter, r *http.Request) {
func toGroupResponse(account *server.Account, group *server.Group) *api.Group { func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
cache := make(map[string]api.PeerMinimum) cache := make(map[string]api.PeerMinimum)
gr := api.Group{ gr := api.Group{
Id: group.ID, Id: group.ID,
Name: group.Name, Name: group.Name,
PeersCount: len(group.Peers), Issued: &group.Issued,
Issued: &group.Issued,
} }
for _, pid := range group.Peers { for _, pid := range group.Peers {
@@ -261,5 +260,8 @@ func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
gr.Peers = append(gr.Peers, peerResp) gr.Peers = append(gr.Peers, peerResp)
} }
} }
gr.PeersCount = len(gr.Peers)
return &gr return &gr
} }

View File

@@ -5,6 +5,8 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
"os"
"strconv"
"time" "time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -150,19 +152,37 @@ func (gm *GoogleWorkspaceManager) GetAllAccounts() (map[string][]*UserData, erro
// getAllUsers returns all users in a Google Workspace account filtered by customer ID. // getAllUsers returns all users in a Google Workspace account filtered by customer ID.
func (gm *GoogleWorkspaceManager) getAllUsers() ([]*UserData, error) { func (gm *GoogleWorkspaceManager) getAllUsers() ([]*UserData, error) {
var usersLimit int64 = 500
if maxUsersLimitEnv := os.Getenv("GOOGLE_WORKSPACE_USERS_LIMIT"); maxUsersLimitEnv != "" {
maxUsersLimit, err := strconv.Atoi(maxUsersLimitEnv)
if err == nil {
log.Debugf("GOOGLE_WORKSPACE_USERS_LIMIT env is set using %d as users limit", maxUsersLimit)
usersLimit = int64(maxUsersLimit)
}
} else {
log.Debugf("GOOGLE_WORKSPACE_USERS_LIMIT env is not set using default users limit 500")
}
users := make([]*UserData, 0) users := make([]*UserData, 0)
pageToken := "" pageToken := ""
for { for {
call := gm.usersService.List().Customer(gm.CustomerID).MaxResults(500) call := gm.usersService.List().Customer(gm.CustomerID).MaxResults(usersLimit)
if pageToken != "" { if pageToken != "" {
call.PageToken(pageToken) call.PageToken(pageToken)
} }
resp, err := call.Do() resp, err := call.Do()
if err != nil { if err != nil {
log.Debugf("failed to retrieve users from workspace error: %s, http status: %d, headers: %v",
err.Error(),
resp.HTTPStatusCode,
resp.Header,
)
return nil, err return nil, err
} }
log.Debugf("fetched %d users from workspace", len(resp.Users))
for _, user := range resp.Users { for _, user := range resp.Users {
users = append(users, parseGoogleWorkspaceUser(user)) users = append(users, parseGoogleWorkspaceUser(user))
} }

View File

@@ -255,8 +255,8 @@ func validateNSGroupName(name, nsGroupID string, nsGroupMap map[string]*nbdns.Na
func validateNSList(list []nbdns.NameServer) error { func validateNSList(list []nbdns.NameServer) error {
nsListLenght := len(list) nsListLenght := len(list)
if nsListLenght == 0 || nsListLenght > 2 { if nsListLenght == 0 || nsListLenght > 3 {
return status.Errorf(status.InvalidArgument, "the list of nameservers should be 1 or 2, got %d", len(list)) return status.Errorf(status.InvalidArgument, "the list of nameservers should be 1 or 3, got %d", len(list))
} }
return nil return nil
} }

View File

@@ -216,7 +216,7 @@ func TestCreateNameServerGroup(t *testing.T) {
shouldCreate: false, shouldCreate: false,
}, },
{ {
name: "Create A NS Group With More Than 2 Nameservers Should Fail", name: "Create A NS Group With More Than 3 Nameservers Should Fail",
inputArgs: input{ inputArgs: input{
name: "super", name: "super",
description: "super", description: "super",
@@ -238,6 +238,11 @@ func TestCreateNameServerGroup(t *testing.T) {
NSType: nbdns.UDPNameServerType, NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort, Port: nbdns.DefaultDNSPort,
}, },
{
IP: netip.MustParseAddr("1.1.4.4"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
},
}, },
enabled: true, enabled: true,
}, },
@@ -457,6 +462,11 @@ func TestSaveNameServerGroup(t *testing.T) {
NSType: nbdns.UDPNameServerType, NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort, Port: nbdns.DefaultDNSPort,
}, },
{
IP: netip.MustParseAddr("1.1.4.4"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
},
} }
invalidID := "doesntExist" invalidID := "doesntExist"
validName := "12345678901234567890qw" validName := "12345678901234567890qw"

View File

@@ -890,18 +890,6 @@ func (am *DefaultAccountManager) SaveOrAddUser(accountID, initiatorUserID string
if err != nil { if err != nil {
return nil, err return nil, err
} }
if userData == nil {
// lets check external cache
key := newUser.IntegrationReference.CacheKey(account.Id, newUser.Id)
log.Debugf("looking up user %s of account %s in external cache", key, account.Id)
info, err := am.externalCacheManager.Get(am.ctx, key)
if err != nil {
log.Infof("Get ExternalCache for key: %s, error: %s", key, err)
return nil, status.Errorf(status.NotFound, "user %s not found in the IdP", newUser.Id)
}
return newUser.ToUserInfo(info)
}
return newUser.ToUserInfo(userData) return newUser.ToUserInfo(userData)
} }
return newUser.ToUserInfo(nil) return newUser.ToUserInfo(nil)