mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 10:06:24 +05:00
272 lines
8.4 KiB
Go
272 lines
8.4 KiB
Go
/*
|
|
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 <http://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package sessions
|
|
|
|
import (
|
|
"errors"
|
|
"math/rand"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cgrates/cgrates/config"
|
|
"github.com/cgrates/cgrates/engine"
|
|
"github.com/cgrates/cgrates/utils"
|
|
"github.com/dgrijalva/jwt-go"
|
|
)
|
|
|
|
var unratedReqs = engine.MapEvent{
|
|
utils.META_POSTPAID: struct{}{},
|
|
utils.META_PSEUDOPREPAID: struct{}{},
|
|
utils.META_RATED: struct{}{},
|
|
}
|
|
|
|
var authReqs = engine.MapEvent{
|
|
utils.META_PREPAID: struct{}{},
|
|
utils.META_PSEUDOPREPAID: struct{}{},
|
|
}
|
|
|
|
// BiRPClient is the interface implemented by Agents which are able to
|
|
// communicate bidirectionally with SessionS and remote Communication Switch
|
|
type BiRPClient interface {
|
|
Call(serviceMethod string, args interface{}, reply interface{}) error
|
|
V1DisconnectSession(args utils.AttrDisconnectSession, reply *string) (err error)
|
|
V1GetActiveSessionIDs(ignParam string, sessionIDs *[]*SessionID) (err error)
|
|
V1ReAuthorize(originID string, reply *string) (err error)
|
|
V1DisconnectPeer(args *utils.DPRArgs, reply *string) (err error)
|
|
DisconnectWarning(args map[string]interface{}, reply *string) (err error)
|
|
}
|
|
|
|
// getSessionTTL retrieves SessionTTL setting out of ev
|
|
// if SessionTTLMaxDelay is present in ev, the return is randomized
|
|
// ToDo: remove if not needed
|
|
func getSessionTTL(ev *engine.MapEvent, cfgSessionTTL time.Duration,
|
|
cfgSessionTTLMaxDelay *time.Duration) (ttl time.Duration, err error) {
|
|
if ttl, err = ev.GetDuration(utils.SessionTTL); err != nil {
|
|
if err != utils.ErrNotFound {
|
|
return
|
|
}
|
|
err = nil
|
|
ttl = cfgSessionTTL
|
|
}
|
|
if ttl == 0 {
|
|
return
|
|
}
|
|
// random delay computation
|
|
var sessionTTLMaxDelay int64
|
|
maxDelay, err := ev.GetDuration(utils.SessionTTLMaxDelay)
|
|
if err != nil {
|
|
if err != utils.ErrNotFound {
|
|
return
|
|
}
|
|
err = nil // clear the error for return
|
|
if cfgSessionTTLMaxDelay != nil {
|
|
maxDelay = *cfgSessionTTLMaxDelay
|
|
}
|
|
}
|
|
sessionTTLMaxDelay = maxDelay.Nanoseconds() / 1000000 // Milliseconds precision for randomness
|
|
if sessionTTLMaxDelay != 0 {
|
|
rand.Seed(time.Now().Unix())
|
|
ttl += time.Duration(rand.Int63n(sessionTTLMaxDelay) * 1000000)
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetSetCGRID will populate the CGRID key if not present and return it
|
|
func GetSetCGRID(ev engine.MapEvent) (cgrID string) {
|
|
cgrID = ev.GetStringIgnoreErrors(utils.CGRID)
|
|
if cgrID == "" {
|
|
cgrID = utils.Sha1(ev.GetStringIgnoreErrors(utils.OriginID),
|
|
ev.GetStringIgnoreErrors(utils.OriginHost))
|
|
ev[utils.CGRID] = cgrID
|
|
}
|
|
return
|
|
}
|
|
|
|
func getFlagIDs(flag string) []string {
|
|
flagWithIDs := strings.Split(flag, utils.InInFieldSep)
|
|
if len(flagWithIDs) <= 1 {
|
|
return nil
|
|
}
|
|
return strings.Split(flagWithIDs[1], utils.INFIELD_SEP)
|
|
}
|
|
|
|
// ProcessedStirIdentity the structure that keeps all the header information
|
|
type ProcessedStirIdentity struct {
|
|
Tokens []string
|
|
SigningStr string
|
|
Signature string
|
|
Header *utils.PASSporTHeader
|
|
Payload *utils.PASSporTPayload
|
|
}
|
|
|
|
// NewProcessedIdentity creates a proccessed header
|
|
func NewProcessedIdentity(identity string) (pi *ProcessedStirIdentity, err error) {
|
|
pi = new(ProcessedStirIdentity)
|
|
hdrtoken := strings.Split(utils.RemoveWhiteSpaces(identity), utils.INFIELD_SEP)
|
|
|
|
if len(hdrtoken) == 1 {
|
|
err = errors.New("missing parts of the message header")
|
|
return
|
|
}
|
|
pi.Tokens = hdrtoken[1:]
|
|
btoken := strings.Split(hdrtoken[0], utils.NestingSep)
|
|
if len(btoken) != 3 {
|
|
err = errors.New("wrong header format")
|
|
return
|
|
}
|
|
pi.SigningStr = btoken[0] + utils.NestingSep + btoken[1]
|
|
pi.Signature = btoken[2]
|
|
|
|
pi.Header = new(utils.PASSporTHeader)
|
|
if err = utils.DecodeBase64JSON(btoken[0], pi.Header); err != nil {
|
|
return
|
|
}
|
|
pi.Payload = new(utils.PASSporTPayload)
|
|
err = utils.DecodeBase64JSON(btoken[1], pi.Payload)
|
|
return
|
|
}
|
|
|
|
// VerifyHeader returns if the header is corectly populated
|
|
func (pi *ProcessedStirIdentity) VerifyHeader() (isValid bool) {
|
|
var x5u string
|
|
for _, pair := range pi.Tokens {
|
|
ptoken := strings.Split(pair, utils.AttrValueSep)
|
|
if len(ptoken) != 2 {
|
|
continue
|
|
}
|
|
switch ptoken[0] {
|
|
case utils.STIRAlgField:
|
|
if ptoken[1] != utils.STIRAlg {
|
|
return false
|
|
}
|
|
case utils.STIRPptField:
|
|
if ptoken[1] != utils.STIRPpt &&
|
|
ptoken[1] != "\""+utils.STIRPpt+"\"" {
|
|
return false
|
|
}
|
|
case utils.STIRInfoField:
|
|
lenParamInfo := len(ptoken[1])
|
|
if lenParamInfo <= 2 {
|
|
return false
|
|
}
|
|
x5u = ptoken[1]
|
|
if x5u[0] == '<' && x5u[lenParamInfo-1] == '>' {
|
|
x5u = x5u[1 : lenParamInfo-1]
|
|
}
|
|
}
|
|
}
|
|
|
|
return pi.Header.Alg == utils.STIRAlg &&
|
|
pi.Header.Ppt == utils.STIRPpt &&
|
|
pi.Header.Typ == utils.STIRTyp &&
|
|
pi.Header.X5u == x5u
|
|
}
|
|
|
|
// VerifySignature returns if the signature is valid
|
|
func (pi *ProcessedStirIdentity) VerifySignature(timeoutVal time.Duration) (err error) {
|
|
var pubkey interface{}
|
|
var ok bool
|
|
if pubkey, ok = engine.Cache.Get(utils.CacheSTIR, pi.Header.X5u); !ok {
|
|
if pubkey, err = utils.NewECDSAPubKey(pi.Header.X5u, timeoutVal); err != nil {
|
|
engine.Cache.Set(utils.CacheSTIR, pi.Header.X5u, nil,
|
|
nil, false, utils.NonTransactional)
|
|
return
|
|
}
|
|
engine.Cache.Set(utils.CacheSTIR, pi.Header.X5u, pubkey,
|
|
nil, false, utils.NonTransactional)
|
|
}
|
|
|
|
sigMethod := jwt.GetSigningMethod(pi.Header.Alg)
|
|
return sigMethod.Verify(pi.SigningStr, pi.Signature, pubkey)
|
|
|
|
}
|
|
|
|
// VerifyPayload returns if the payload is corectly populated
|
|
func (pi *ProcessedStirIdentity) VerifyPayload(originatorTn, originatorURI, destinationTn, destinationURI string,
|
|
hdrMaxDur time.Duration, attest *utils.StringSet) (err error) {
|
|
if !attest.Has(utils.META_ANY) && !attest.Has(pi.Payload.ATTest) {
|
|
return errors.New("wrong attest level")
|
|
}
|
|
if hdrMaxDur >= 0 && time.Now().After(time.Unix(pi.Payload.IAT, 0).Add(hdrMaxDur)) {
|
|
return errors.New("expired payload")
|
|
}
|
|
if originatorURI != utils.EmptyString {
|
|
if originatorURI != pi.Payload.Orig.URI {
|
|
return errors.New("wrong originatorURI")
|
|
}
|
|
} else if originatorTn != pi.Payload.Orig.Tn {
|
|
return errors.New("wrong originatorTn")
|
|
}
|
|
if destinationURI != utils.EmptyString {
|
|
if !utils.SliceHasMember(pi.Payload.Dest.URI, destinationURI) {
|
|
return errors.New("wrong destinationURI")
|
|
}
|
|
} else if !utils.SliceHasMember(pi.Payload.Dest.Tn, destinationTn) {
|
|
return errors.New("wrong destinationTn")
|
|
}
|
|
return
|
|
}
|
|
|
|
// NewIdentity returns the identiy for stir header
|
|
func NewIdentity(header *utils.PASSporTHeader, payload *utils.PASSporTPayload, prvkeyPath string, timeout time.Duration) (identity string, err error) {
|
|
var prvKey interface{}
|
|
var ok bool
|
|
if prvKey, ok = engine.Cache.Get(utils.CacheSTIR, prvkeyPath); !ok {
|
|
if prvKey, err = utils.NewECDSAPrvKey(prvkeyPath, timeout); err != nil {
|
|
engine.Cache.Set(utils.CacheSTIR, prvkeyPath, nil,
|
|
nil, false, utils.NonTransactional)
|
|
return
|
|
}
|
|
engine.Cache.Set(utils.CacheSTIR, prvkeyPath, prvKey,
|
|
nil, false, utils.NonTransactional)
|
|
}
|
|
var headerStr, payloadStr string
|
|
if headerStr, err = utils.EncodeBase64JSON(header); err != nil {
|
|
return
|
|
}
|
|
if payloadStr, err = utils.EncodeBase64JSON(payload); err != nil {
|
|
return
|
|
}
|
|
identity = headerStr + utils.NestingSep + payloadStr
|
|
|
|
sigMethod := jwt.GetSigningMethod(header.Alg)
|
|
var signature string
|
|
if signature, err = sigMethod.Sign(identity, prvKey); err != nil {
|
|
return
|
|
}
|
|
identity += utils.NestingSep + signature
|
|
identity += utils.STIRExtraInfoPrefix + header.X5u + utils.STIRExtraInfoSuffix
|
|
return
|
|
}
|
|
|
|
func AuthStirShaken(identity, originatorTn, originatorURI, destinationTn, destinationURI string,
|
|
attest *utils.StringSet, hdrMaxDur time.Duration) (err error) {
|
|
var pi *ProcessedStirIdentity
|
|
if pi, err = NewProcessedIdentity(identity); err != nil {
|
|
return
|
|
}
|
|
if !pi.VerifyHeader() {
|
|
return errors.New("wrong header")
|
|
}
|
|
if err = pi.VerifySignature(config.CgrConfig().GeneralCfg().ReplyTimeout); err != nil {
|
|
return
|
|
}
|
|
return pi.VerifyPayload(originatorTn, originatorURI, destinationTn, destinationURI, hdrMaxDur, attest)
|
|
}
|