diff --git a/config/apiban.go b/config/apiban.go
index fccd4ed43..f43eac3f9 100644
--- a/config/apiban.go
+++ b/config/apiban.go
@@ -55,36 +55,3 @@ func (ban APIBanCfg) Clone() (cln *APIBanCfg) {
}
return
}
-
-type SentryPeerCfg struct {
- Token string
- Url string
-}
-
-func (sp *SentryPeerCfg) loadFromJSONCfg(jsnCfg *SentryPeerJsonCfg) (err error) {
- if jsnCfg == nil {
- return
- }
- if jsnCfg.Url != nil {
- sp.Url = *jsnCfg.Url
- }
- if jsnCfg.Token != nil {
- sp.Token = *jsnCfg.Token
- }
- return
-}
-
-func (sp *SentryPeerCfg) AsMapInterface() map[string]any {
- return map[string]any{
- "URL": sp.Url,
- "Token": sp.Token,
- }
-}
-
-func (sp *SentryPeerCfg) Clone() (cln *SentryPeerCfg) {
- cln = &SentryPeerCfg{
- Url: sp.Url,
- Token: sp.Token,
- }
- return
-}
diff --git a/config/config_defaults.go b/config/config_defaults.go
index c9532d5b4..e539fc7a4 100644
--- a/config/config_defaults.go
+++ b/config/config_defaults.go
@@ -304,6 +304,7 @@ const CGRATES_CFG_JSON = `
"*uch": {"limit": -1, "ttl": "3h", "static_ttl": false, "remote":false, "replicate": false}, // User cache
"*stir": {"limit": -1, "ttl": "3h", "static_ttl": false, "remote":false, "replicate": false}, // stirShaken cache keys
"*apiban":{"limit": -1, "ttl": "2m", "static_ttl": false, "remote":false, "replicate": false},
+ "*sentrypeer":{"limit": -1, "ttl": "86400s", "static_ttl": true, "remote":false, "replicate": false},
"*caps_events": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, // caps cached samples
"*replication_hosts": {"limit": 0, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, // the replication hosts cache(used when replication_filtered is enbled)
},
@@ -1268,7 +1269,8 @@ const CGRATES_CFG_JSON = `
},
"sentrypeer":{
"url":"",
- "token":"",
+ "clientID":"",
+ "clientSecret":"",
}
}`
diff --git a/config/libconfig_json.go b/config/libconfig_json.go
index 7c5912b38..9c1d760ff 100644
--- a/config/libconfig_json.go
+++ b/config/libconfig_json.go
@@ -877,8 +877,9 @@ type APIBanJsonCfg struct {
}
type SentryPeerJsonCfg struct {
- Token *string
- Url *string
+ ClientID *string
+ ClientSecret *string
+ Url *string
}
type CoreSJsonCfg struct {
diff --git a/config/sentrypeer.go b/config/sentrypeer.go
new file mode 100644
index 000000000..b1b73f85c
--- /dev/null
+++ b/config/sentrypeer.go
@@ -0,0 +1,58 @@
+/*
+Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
+Copyright (C) ITsysCOM GmbH
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+*/
+
+package config
+
+type SentryPeerCfg struct {
+ ClientID string
+ ClientSecret string
+ Url string
+}
+
+func (sp *SentryPeerCfg) loadFromJSONCfg(jsnCfg *SentryPeerJsonCfg) (err error) {
+ if jsnCfg == nil {
+ return
+ }
+ if jsnCfg.Url != nil {
+ sp.Url = *jsnCfg.Url
+ }
+ if jsnCfg.ClientSecret != nil {
+ sp.ClientSecret = *jsnCfg.ClientSecret
+ }
+ if jsnCfg.ClientID != nil {
+ sp.ClientID = *jsnCfg.ClientID
+ }
+ return
+}
+
+func (sp *SentryPeerCfg) AsMapInterface() map[string]any {
+ return map[string]any{
+ "URL": sp.Url,
+ "ClientSecret": sp.ClientSecret,
+ "ClientID": sp.ClientID,
+ }
+}
+
+func (sp *SentryPeerCfg) Clone() (cln *SentryPeerCfg) {
+ cln = &SentryPeerCfg{
+ Url: sp.Url,
+ ClientSecret: sp.ClientSecret,
+ ClientID: sp.ClientID,
+ }
+ return
+}
diff --git a/data/conf/samples/tutinternal/cgrates.json b/data/conf/samples/tutinternal/cgrates.json
index 422056db8..2382045c3 100644
--- a/data/conf/samples/tutinternal/cgrates.json
+++ b/data/conf/samples/tutinternal/cgrates.json
@@ -111,9 +111,11 @@
"apiers_conns": ["*internal"]
}
-// "sentrypeer":{
-// "url":"https://sentrypeer.com/api/",
-// "token":"put token here",
-// },
+ //"sentrypeer":{
+ //"url":"https://sentrypeer.com/api/",
+ //"clientID":"",
+ //"clientSecret":"",
+ //},
+
}
diff --git a/data/conf/samples/tutmongo/cgrates.json b/data/conf/samples/tutmongo/cgrates.json
index 479fd592f..8ccbcc4dc 100644
--- a/data/conf/samples/tutmongo/cgrates.json
+++ b/data/conf/samples/tutmongo/cgrates.json
@@ -127,9 +127,10 @@
"apiers_conns": ["*internal"],
},
- //"sentrypeer":{
- //"url":"https://sentrypeer.com/api/",
- //"token":""
- //},
+// "sentrypeer":{
+// "url":"https://sentrypeer.com/api/",
+// "clientID":"",
+// "clientSecret":"",
+// },
}
diff --git a/data/conf/samples/tutmysql/cgrates.json b/data/conf/samples/tutmysql/cgrates.json
index 5c1db20ec..7b20de59a 100644
--- a/data/conf/samples/tutmysql/cgrates.json
+++ b/data/conf/samples/tutmysql/cgrates.json
@@ -118,9 +118,10 @@
"apiers_conns": ["*internal"],
},
-// "sentrypeer":{
-// "url":"https://sentrypeer.com/api/",
-// "token":"put token here",
-// },
+ //"sentrypeer":{
+ //"url":"https://sentrypeer.com/api/",
+ //"clientID":"",
+ //"clientSecret":"",
+ //},
}
diff --git a/data/conf/samples/tutpostgres/cgrates.json b/data/conf/samples/tutpostgres/cgrates.json
index 138d9e856..edf8600f6 100644
--- a/data/conf/samples/tutpostgres/cgrates.json
+++ b/data/conf/samples/tutpostgres/cgrates.json
@@ -103,9 +103,10 @@
"apiers_conns": ["*internal"],
},
-// "sentrypeer":{
-// "url":"https://sentrypeer.com/api/",
-// "token":"put token here",
-// },
+ //"sentrypeer":{
+ //"url":"https://sentrypeer.com/api/",
+ //"clientID":"",
+ //"clientSecret":"",
+ //},
}
diff --git a/engine/datamanager.go b/engine/datamanager.go
index 4d61511c1..c170854ce 100644
--- a/engine/datamanager.go
+++ b/engine/datamanager.go
@@ -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().Token, utils.EmptyString, false, true)
+ _, err = GetSentryPeer(utils.EmptyString, config.CgrConfig().SentryPeerCfg().Url, config.CgrConfig().SentryPeerCfg().ClientID, config.CgrConfig().SentryPeerCfg().ClientSecret, utils.EmptyString, false, true)
}
if err != nil {
if err != utils.ErrNotFound && err != utils.ErrDSPProfileNotFound && err != utils.ErrDSPHostNotFound {
diff --git a/engine/filterhelpers.go b/engine/filterhelpers.go
index 431455723..b9e47b6bd 100644
--- a/engine/filterhelpers.go
+++ b/engine/filterhelpers.go
@@ -19,7 +19,10 @@ along with this program. If not, see
package engine
import (
+ "bytes"
+ "encoding/json"
"fmt"
+ "io"
"net/http"
"github.com/cgrates/cgrates/config"
@@ -27,6 +30,10 @@ 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
@@ -120,13 +127,51 @@ func WeightFromDynamics(dWs []*utils.DynamicWeight,
}
return 0.0, nil
}
-
-func GetSentryPeer(val, url, token, path string, cacheRead, cacheWrite bool) (found bool, err error) {
+func getToken(clientID, clientSecret string, cacheWrite bool, token *TokenResponse) (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",
+ }
+ 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":
@@ -134,46 +179,57 @@ func GetSentryPeer(val, url, token, path string, cacheRead, cacheWrite bool) (fo
case "*number":
url += "phone-numbers/"
}
- var req *http.Request
- if req, err = http.NewRequest("GET", url+val, nil); err != nil {
+ resp, err = getHTTP("GET", url+val, nil, map[string][]string{"Authorization": {fmt.Sprintf("Bearer %v", token.AccessToken)}})
+ if err != nil {
return
}
- if token != "" {
- req.Header = http.Header{
- "Authorization": {fmt.Sprintf("Bearer %s", token)},
+ 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 {
+ return
+ }
+ resp, err = getHTTP("GET", url+val, nil, map[string][]string{"Authorization": {fmt.Sprintf("Bearer %v", token.AccessToken)}})
+ if 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")
}
}
- var resp *http.Response
- resp, err = http.DefaultClient.Do(req)
- if err != nil {
- return false, err
- }
- defer resp.Body.Close()
- if resp.StatusCode >= 200 && resp.StatusCode < 300 {
+ 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 {
+ return
+ }
+ }
+ 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
- } else {
- switch {
- case resp.StatusCode == http.StatusNotFound:
- if cacheWrite {
- if err = Cache.Set(utils.MetaSentryPeer, valpath, false, nil, true, utils.NonTransactional); err != nil {
- return false, err
- }
- }
- return
- case resp.StatusCode >= 400 && resp.StatusCode < 500:
- err = fmt.Errorf("client error<%s>", resp.Status)
- return
- case resp.StatusCode >= 500:
- err = fmt.Errorf("server error<%s>", resp.Status)
- return
- default:
- err = fmt.Errorf("unexpected status code<%s>", resp.Status)
- }
+ case resp.StatusCode >= http.StatusBadRequest && resp.StatusCode < http.StatusInternalServerError:
+ err = fmt.Errorf("client err <%v>", resp.Status)
+ case resp.StatusCode >= http.StatusInternalServerError:
+ err = fmt.Errorf("server error<%s>", resp.Status)
+ default:
+ err = fmt.Errorf("unexpected status code<%s>", resp.Status)
}
- return
+ utils.Logger.Warning(fmt.Sprintf("Sentrypeer filter got %v ", err.Error()))
+ return false, err
+}
+
+func getHTTP(method, url string, payload io.Reader, headers map[string][]string) (resp *http.Response, err error) {
+ var req *http.Request
+ if req, err = http.NewRequest(method, url, payload); err != nil {
+ return
+ }
+ req.Header = headers
+ return http.DefaultClient.Do(req)
}
diff --git a/engine/filters.go b/engine/filters.go
index 5c8890995..aba146666 100644
--- a/engine/filters.go
+++ b/engine/filters.go
@@ -659,7 +659,7 @@ func (fltr *FilterRule) passSentryPeer(dDP utils.DataProvider) (bool, error) {
if fltr.Values[0] != "*number" && fltr.Values[0] != "*ip" {
return false, fmt.Errorf("invalid value for sentrypeer filter: <%s>", fltr.Values[0])
}
- return GetSentryPeer(strVal, config.CgrConfig().SentryPeerCfg().Url, config.CgrConfig().SentryPeerCfg().Token, fltr.Values[0], true, true)
+ return GetSentryPeer(strVal, config.CgrConfig().SentryPeerCfg().Url, config.CgrConfig().SentryPeerCfg().ClientID, config.CgrConfig().SentryPeerCfg().ClientSecret, fltr.Values[0], true, true)
}
func parseTime(rsr *config.RSRParser, dDp utils.DataProvider) (_ time.Time, err error) {