Added new fields in sentrypeerCfg && implemented fallback loop on unauthorized requests

This commit is contained in:
gezimbll
2023-07-14 10:56:26 -04:00
committed by Dan Christian Bogos
parent 9af02771fd
commit 2e93354ad1
11 changed files with 135 additions and 104 deletions

View File

@@ -1268,9 +1268,13 @@ const CGRATES_CFG_JSON = `
"keys": [],
},
"sentrypeer":{
"url":"",
"clientID":"",
"clientSecret":"",
}
"client_id":"",
"client_secret":"",
"token_url":"https://authz.sentrypeer.com/oauth/token",
"ip_url":"https://sentrypeer.com/api/ip-addresses",
"number_url":"https://sentrypeer.com/api/phone-numbers",
"audience":"https://sentrypeer.com/api",
"grant_type":"client_credentials",
},
}`

View File

@@ -877,9 +877,13 @@ type APIBanJsonCfg struct {
}
type SentryPeerJsonCfg struct {
ClientID *string
ClientSecret *string
Url *string
ClientID *string `json:"client_id"`
ClientSecret *string `json:"client_secret"`
TokenUrl *string `json:"token_url"`
IpUrl *string `json:"ip_url"`
NumberUrl *string `json:"number_url"`
Audience *string `json:"audience"`
GrantType *string `json:"grant_type"`
}
type CoreSJsonCfg struct {

View File

@@ -21,15 +21,25 @@ package config
type SentryPeerCfg struct {
ClientID string
ClientSecret string
Url string
TokenUrl string
IpUrl string
NumberUrl string
Audience string
GrantType string
}
func (sp *SentryPeerCfg) loadFromJSONCfg(jsnCfg *SentryPeerJsonCfg) (err error) {
if jsnCfg == nil {
return
}
if jsnCfg.Url != nil {
sp.Url = *jsnCfg.Url
if jsnCfg.TokenUrl != nil {
sp.TokenUrl = *jsnCfg.TokenUrl
}
if jsnCfg.IpUrl != nil {
sp.IpUrl = *jsnCfg.IpUrl
}
if jsnCfg.NumberUrl != nil {
sp.NumberUrl = *jsnCfg.NumberUrl
}
if jsnCfg.ClientSecret != nil {
sp.ClientSecret = *jsnCfg.ClientSecret
@@ -37,22 +47,36 @@ func (sp *SentryPeerCfg) loadFromJSONCfg(jsnCfg *SentryPeerJsonCfg) (err error)
if jsnCfg.ClientID != nil {
sp.ClientID = *jsnCfg.ClientID
}
if jsnCfg.Audience != nil {
sp.Audience = *jsnCfg.Audience
}
if jsnCfg.GrantType != nil {
sp.GrantType = *jsnCfg.GrantType
}
return
}
func (sp *SentryPeerCfg) AsMapInterface() map[string]any {
return map[string]any{
"URL": sp.Url,
"TokenURL": sp.TokenUrl,
"ClientSecret": sp.ClientSecret,
"ClientID": sp.ClientID,
"IpUrl": sp.IpUrl,
"NumberUrl": sp.NumberUrl,
"Audience": sp.Audience,
"GrantType": sp.GrantType,
}
}
func (sp *SentryPeerCfg) Clone() (cln *SentryPeerCfg) {
cln = &SentryPeerCfg{
Url: sp.Url,
TokenUrl: sp.TokenUrl,
ClientSecret: sp.ClientSecret,
ClientID: sp.ClientID,
IpUrl: sp.IpUrl,
NumberUrl: sp.NumberUrl,
Audience: sp.Audience,
GrantType: sp.GrantType,
}
return
}

View File

@@ -111,11 +111,5 @@
"apiers_conns": ["*internal"]
}
//"sentrypeer":{
//"url":"https://sentrypeer.com/api/",
//"clientID":"",
//"clientSecret":"",
//},
}

View File

@@ -128,9 +128,13 @@
},
// "sentrypeer":{
// "url":"https://sentrypeer.com/api/",
// "clientID":"",
// "clientSecret":"",
// },
// "client_id":"",
// "client_secret":"",
// "token_url":"",
// "ip_url":"",
// "number_url":"",
// "audience":"",
// "grant_type":"",
// },
}

View File

@@ -118,10 +118,5 @@
"apiers_conns": ["*internal"],
},
//"sentrypeer":{
//"url":"https://sentrypeer.com/api/",
//"clientID":"",
//"clientSecret":"",
//},
}

View File

@@ -270,7 +270,7 @@ func (dm *DataManager) CacheDataFromDB(prfx string, ids []string, mustBeCached b
case utils.MetaAPIBan:
_, err = dm.GetAPIBan(utils.EmptyString, config.CgrConfig().APIBanCfg().Keys, false, false, true)
case utils.MetaSentryPeer:
_, err = GetSentryPeer(utils.EmptyString, config.CgrConfig().SentryPeerCfg().Url, config.CgrConfig().SentryPeerCfg().ClientID, config.CgrConfig().SentryPeerCfg().ClientSecret, utils.EmptyString, false, true)
_, err = GetSentryPeer(utils.EmptyString, config.CgrConfig().SentryPeerCfg(), utils.EmptyString)
}
if err != nil {
if err != utils.ErrNotFound && err != utils.ErrDSPProfileNotFound && err != utils.ErrDSPHostNotFound {

View File

@@ -30,10 +30,6 @@ import (
"github.com/cgrates/cgrates/utils"
)
type TokenResponse struct {
AccessToken string `json:"access_token"`
}
// MatchingItemIDsForEvent returns the list of item IDs matching fieldName/fieldValue for an event
// fieldIDs limits the fields which are checked against indexes
// helper on top of dataDB.GetIndexes, adding utils.MetaAny to list of fields queried
@@ -127,101 +123,107 @@ func WeightFromDynamics(dWs []*utils.DynamicWeight,
}
return 0.0, nil
}
func getToken(clientID, clientSecret string, cacheWrite bool, token *TokenResponse) (err error) {
func getToken(tokenUrl, clientID, clientSecret, audience, grantType string) (token string, err error) {
var resp *http.Response
payload := map[string]string{
"client_id": clientID,
"client_secret": clientSecret,
"audience": "https://sentrypeer.com/api",
"grant_type": "client_credentials",
"audience": audience,
"grant_type": grantType,
}
jsonPayload, _ := json.Marshal(payload)
resp, err = getHTTP("POST", "https://authz.sentrypeer.com/oauth/token", bytes.NewBuffer(jsonPayload), map[string][]string{"Content-Type": {"application/json"}})
if err != nil {
return err
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&token)
if err != nil {
return err
}
if cacheWrite {
if err = Cache.Set(utils.MetaSentryPeer, "*token", token.AccessToken, nil, true, utils.NonTransactional); err != nil {
return err
}
}
return
}
func GetSentryPeer(val, url, clientID, clientSecret, path string, cacheRead, cacheWrite bool) (found bool, err error) {
valpath := utils.ConcatenatedKey(path, val)
var (
token TokenResponse
resp *http.Response
cachedReq bool
)
if cacheRead {
if x, ok := Cache.Get(utils.MetaSentryPeer, valpath); ok && x != nil { // Attempt to find in cache first
return x.(bool), nil
}
var cachedToken any
if cachedToken, cachedReq = Cache.Get(utils.MetaSentryPeer, "*token"); cachedReq && cachedToken != nil {
token.AccessToken = cachedToken.(string)
}
}
if !cachedReq {
if err = getToken(clientID, clientSecret, cacheWrite, &token); err != nil {
return
}
}
switch path {
case "*ip":
url += "ip-addresses/"
case "*number":
url += "phone-numbers/"
}
resp, err = getHTTP("GET", url+val, nil, map[string][]string{"Authorization": {fmt.Sprintf("Bearer %v", token.AccessToken)}})
resp, err = getHTTP("POST", tokenUrl, bytes.NewBuffer(jsonPayload), map[string][]string{"Content-Type": {"application/json"}})
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
Cache.Remove(utils.MetaSentryPeer, "*token", true, utils.NonTransactional)
utils.Logger.Warning("SentryPeer redirecting to new bearer token ")
if err = getToken(clientID, clientSecret, cacheWrite, &token); err != nil {
var m struct {
AccessToken string `json:"access_token"`
}
err = json.NewDecoder(resp.Body).Decode(&m)
if err != nil {
return
}
token = m.AccessToken
return
}
func GetSentryPeer(val string, sentryPeerCfg *config.SentryPeerCfg, path string) (found bool, err error) {
itemId := utils.ConcatenatedKey(path, val)
var (
isCached bool
url string
token string
)
if x, ok := Cache.Get(utils.MetaSentryPeer, itemId); ok && x != nil { // Attempt to find in cache first
return x.(bool), nil
}
var cachedToken any
if cachedToken, isCached = Cache.Get(utils.MetaSentryPeer,
utils.MetaToken); isCached && cachedToken != nil {
token = cachedToken.(string)
}
switch path {
case utils.MetaIp:
url = sentryPeerCfg.IpUrl + "/" + val
case utils.MetaNumber:
url = sentryPeerCfg.NumberUrl + "/" + val
}
if !isCached {
if token, err = getToken(sentryPeerCfg.TokenUrl, sentryPeerCfg.ClientID, sentryPeerCfg.ClientSecret,
sentryPeerCfg.Audience, sentryPeerCfg.GrantType); err != nil {
return
}
resp, err = getHTTP("GET", url+val, nil, map[string][]string{"Authorization": {fmt.Sprintf("Bearer %v", token.AccessToken)}})
if err != nil {
if err = Cache.Set(utils.MetaSentryPeer, utils.MetaToken,
token, nil, true, utils.NonTransactional); err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
return false, fmt.Errorf("still unauthorized after getting new token")
}
}
switch {
case resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices:
if cacheWrite {
if err = Cache.Set(utils.MetaSentryPeer, valpath, false, nil, true, utils.NonTransactional); err != nil {
for i := 0; i < 2; i++ {
if found, err = hasData(itemId, token, url); err == nil {
if err = Cache.Set(utils.MetaSentryPeer, itemId, found,
nil, true, utils.NonTransactional); err != nil {
return
}
break
} else if err != utils.ErrNotAuthorized {
break
}
Cache.Remove(utils.MetaSentryPeer, utils.MetaToken, true, utils.EmptyString)
if token, err = getToken(sentryPeerCfg.TokenUrl, sentryPeerCfg.ClientID, sentryPeerCfg.ClientSecret,
sentryPeerCfg.Audience, sentryPeerCfg.GrantType); err != nil {
return
}
if err = Cache.Set(utils.MetaSentryPeer, utils.MetaToken, token,
nil, true, utils.NonTransactional); err != nil {
return
}
}
return
}
func hasData(itemId, token, url string) (found bool, err error) {
var resp *http.Response
resp, err = getHTTP("GET", url, nil, map[string][]string{"Authorization": {fmt.Sprintf("Bearer %v", token)}})
if err != nil {
return
}
defer resp.Body.Close()
switch {
case resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden:
return false, utils.ErrNotAuthorized
case resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices:
return false, nil
case resp.StatusCode == http.StatusNotFound:
if cacheWrite {
if err = Cache.Set(utils.MetaSentryPeer, valpath, true, nil, true, utils.NonTransactional); err != nil {
return false, err
}
}
return true, nil
case resp.StatusCode >= http.StatusBadRequest && resp.StatusCode < http.StatusInternalServerError:
err = fmt.Errorf("client err <%v>", resp.Status)
err = fmt.Errorf("sentrypeer api got client err <%v>", resp.Status)
case resp.StatusCode >= http.StatusInternalServerError:
err = fmt.Errorf("server error<%s>", resp.Status)
err = fmt.Errorf("sentrypeer api got server error<%s>", resp.Status)
default:
err = fmt.Errorf("unexpected status code<%s>", resp.Status)
err = fmt.Errorf("sentrypeer api got unexpected status code<%s>", resp.Status)
}
utils.Logger.Warning(fmt.Sprintf("Sentrypeer filter got %v ", err.Error()))
utils.Logger.Warning(err.Error())
return false, err
}

View File

@@ -656,10 +656,10 @@ func (fltr *FilterRule) passSentryPeer(dDP utils.DataProvider) (bool, error) {
}
return false, err
}
if fltr.Values[0] != "*number" && fltr.Values[0] != "*ip" {
if fltr.Values[0] != utils.MetaNumber && fltr.Values[0] != utils.MetaIp {
return false, fmt.Errorf("invalid value for sentrypeer filter: <%s>", fltr.Values[0])
}
return GetSentryPeer(strVal, config.CgrConfig().SentryPeerCfg().Url, config.CgrConfig().SentryPeerCfg().ClientID, config.CgrConfig().SentryPeerCfg().ClientSecret, fltr.Values[0], true, true)
return GetSentryPeer(strVal, config.CgrConfig().SentryPeerCfg(), fltr.Values[0])
}
func parseTime(rsr *config.RSRParser, dDp utils.DataProvider) (_ time.Time, err error) {

View File

@@ -1159,6 +1159,9 @@ const (
MetaIPNet = "*ipnet"
MetaAPIBan = "*apiban"
MetaSentryPeer = "*sentrypeer"
MetaToken = "*token"
MetaIp = "*ip"
MetaNumber = "*number"
MetaActivationInterval = "*ai"
MetaRegex = "*regex"

View File

@@ -67,6 +67,7 @@ var (
RalsErrorPrfx = "RALS_ERROR"
DispatcherErrorPrefix = "DISPATCHER_ERROR"
RateSErrPrfx = "RATES_ERROR"
ErrNotAuthorized = errors.New("NOT_AUTHORIZED")
ErrUnsupportedFormat = errors.New("UNSUPPORTED_FORMAT")
ErrNoDatabaseConn = errors.New("NO_DATABASE_CONNECTION")
ErrMaxIncrementsExceeded = errors.New("MAX_INCREMENTS_EXCEEDED")