github.com/anuvu/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/mw_organisation_activity.go (about)

     1  package gateway
     2  
     3  import (
     4  	"errors"
     5  	"net/http"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/TykTechnologies/tyk/ctx"
    10  	"github.com/TykTechnologies/tyk/request"
    11  	"github.com/TykTechnologies/tyk/user"
    12  )
    13  
    14  type orgChanMapMu struct {
    15  	sync.Mutex
    16  	channels map[string](chan bool)
    17  }
    18  
    19  var orgChanMap = orgChanMapMu{channels: map[string](chan bool){}}
    20  var orgActiveMap sync.Map
    21  
    22  // RateLimitAndQuotaCheck will check the incoming request and key whether it is within it's quota and
    23  // within it's rate limit, it makes use of the SessionLimiter object to do this
    24  type OrganizationMonitor struct {
    25  	BaseMiddleware
    26  	sessionlimiter SessionLimiter
    27  	mon            Monitor
    28  }
    29  
    30  func (k *OrganizationMonitor) Name() string {
    31  	return "OrganizationMonitor"
    32  }
    33  
    34  func (k *OrganizationMonitor) EnabledForSpec() bool {
    35  	// If false, we aren't enforcing quotas so skip this mw
    36  	// altogether
    37  	return k.Spec.GlobalConfig.EnforceOrgQuotas
    38  }
    39  
    40  func (k *OrganizationMonitor) getOrgHasNoSession() bool {
    41  	k.Spec.RLock()
    42  	defer k.Spec.RUnlock()
    43  	return k.Spec.OrgHasNoSession
    44  }
    45  
    46  func (k *OrganizationMonitor) setOrgHasNoSession(val bool) {
    47  	k.Spec.Lock()
    48  	defer k.Spec.Unlock()
    49  	k.Spec.OrgHasNoSession = val
    50  }
    51  
    52  func (k *OrganizationMonitor) ProcessRequest(w http.ResponseWriter, r *http.Request, conf interface{}) (error, int) {
    53  	// Skip rate limiting and quotas for looping
    54  	if !ctxCheckLimits(r) {
    55  		return nil, http.StatusOK
    56  	}
    57  
    58  	// short path for specs which have organization limiter enabled but organization has no session
    59  	if k.getOrgHasNoSession() {
    60  		return nil, http.StatusOK
    61  	}
    62  
    63  	var orgSession user.SessionState
    64  	var found bool
    65  
    66  	// try to check in in-app cache 1st
    67  	if !k.Spec.GlobalConfig.LocalSessionCache.DisableCacheSessionState {
    68  		var cachedSession interface{}
    69  		if cachedSession, found = SessionCache.Get(k.Spec.OrgID); found {
    70  			orgSession = cachedSession.(user.SessionState)
    71  		}
    72  	}
    73  
    74  	// try to get from Redis
    75  	if !found {
    76  		// not found in in-app cache, let's read from Redis
    77  		orgSession, found = k.OrgSession(k.Spec.OrgID)
    78  		if !found {
    79  			// prevent reads from in-app cache and from Redis for next runs
    80  			k.setOrgHasNoSession(true)
    81  			// No organisation session has not been created, should not be a pre-requisite in site setups, so we pass the request on
    82  			return nil, http.StatusOK
    83  		}
    84  	}
    85  
    86  	if k.Spec.GlobalConfig.ExperimentalProcessOrgOffThread {
    87  		// Make a copy of request before before sending to goroutine
    88  		r2 := r.WithContext(r.Context())
    89  		return k.ProcessRequestOffThread(r2, orgSession)
    90  	}
    91  	return k.ProcessRequestLive(r, orgSession)
    92  }
    93  
    94  // ProcessRequest will run any checks on the request on the way through the system, return an error to have the chain fail
    95  func (k *OrganizationMonitor) ProcessRequestLive(r *http.Request, orgSession user.SessionState) (error, int) {
    96  	logger := k.Logger()
    97  
    98  	if orgSession.IsInactive {
    99  		logger.Warning("Organisation access is disabled.")
   100  
   101  		return errors.New("this organisation access has been disabled, please contact your API administrator"), http.StatusForbidden
   102  	}
   103  
   104  	// We found a session, apply the quota and rate limiter
   105  	reason := k.sessionlimiter.ForwardMessage(
   106  		r,
   107  		&orgSession,
   108  		k.Spec.OrgID,
   109  		k.Spec.OrgSessionManager.Store(),
   110  		orgSession.Per > 0 && orgSession.Rate > 0,
   111  		true,
   112  		&k.Spec.GlobalConfig,
   113  		k.Spec.APIID,
   114  		false,
   115  	)
   116  
   117  	sessionLifeTime := orgSession.Lifetime(k.Spec.SessionLifetime)
   118  
   119  	if err := k.Spec.OrgSessionManager.UpdateSession(k.Spec.OrgID, &orgSession, sessionLifeTime, false); err == nil {
   120  		// update in-app cache if needed
   121  		if !k.Spec.GlobalConfig.LocalSessionCache.DisableCacheSessionState {
   122  			SessionCache.Set(k.Spec.OrgID, orgSession, time.Second*time.Duration(sessionLifeTime))
   123  		}
   124  	} else {
   125  		logger.WithError(err).Error("Could not update org session")
   126  	}
   127  
   128  	switch reason {
   129  	case sessionFailNone:
   130  		// all good, keep org active
   131  	case sessionFailQuota:
   132  		logger.Warning("Organisation quota has been exceeded.")
   133  
   134  		// Fire a quota exceeded event
   135  		k.FireEvent(
   136  			EventOrgQuotaExceeded,
   137  			EventKeyFailureMeta{
   138  				EventMetaDefault: EventMetaDefault{
   139  					Message:            "Organisation quota has been exceeded",
   140  					OriginatingRequest: EncodeRequestToEvent(r),
   141  				},
   142  				Path:   r.URL.Path,
   143  				Origin: request.RealIP(r),
   144  				Key:    k.Spec.OrgID,
   145  			})
   146  
   147  		return errors.New("This organisation quota has been exceeded, please contact your API administrator"), http.StatusForbidden
   148  	case sessionFailRateLimit:
   149  		logger.Warning("Organisation rate limit has been exceeded.")
   150  
   151  		// Fire a rate limit exceeded event
   152  		k.FireEvent(
   153  			EventOrgRateLimitExceeded,
   154  			EventKeyFailureMeta{
   155  				EventMetaDefault: EventMetaDefault{
   156  					Message:            "Organisation rate limit has been exceeded",
   157  					OriginatingRequest: EncodeRequestToEvent(r),
   158  				},
   159  				Path:   r.URL.Path,
   160  				Origin: request.RealIP(r),
   161  				Key:    k.Spec.OrgID,
   162  			},
   163  		)
   164  		return errors.New("This organisation rate limit has been exceeded, please contact your API administrator"), http.StatusForbidden
   165  	}
   166  
   167  	if k.Spec.GlobalConfig.Monitor.MonitorOrgKeys {
   168  		// Run the trigger monitor
   169  		k.mon.Check(&orgSession, "")
   170  	}
   171  
   172  	// Lets keep a reference of the org
   173  	setCtxValue(r, ctx.OrgSessionContext, orgSession)
   174  
   175  	// Request is valid, carry on
   176  	return nil, http.StatusOK
   177  }
   178  
   179  func (k *OrganizationMonitor) SetOrgSentinel(orgChan chan bool, orgId string) {
   180  	for isActive := range orgChan {
   181  		k.Logger().Debug("Chan got:", isActive)
   182  		orgActiveMap.Store(orgId, isActive)
   183  	}
   184  }
   185  
   186  func (k *OrganizationMonitor) ProcessRequestOffThread(r *http.Request, orgSession user.SessionState) (error, int) {
   187  	orgChanMap.Lock()
   188  	orgChan, ok := orgChanMap.channels[k.Spec.OrgID]
   189  	if !ok {
   190  		orgChan = make(chan bool)
   191  		orgChanMap.channels[k.Spec.OrgID] = orgChan
   192  		go k.SetOrgSentinel(orgChan, k.Spec.OrgID)
   193  	}
   194  	orgChanMap.Unlock()
   195  	active, found := orgActiveMap.Load(k.Spec.OrgID)
   196  
   197  	// Lets keep a reference of the org
   198  	// session might be updated by go-routine AllowAccessNext and we loose those changes here
   199  	// but it is OK as we need it in context for detailed org logging
   200  	setCtxValue(r, ctx.OrgSessionContext, orgSession)
   201  
   202  	orgSessionCopy := orgSession
   203  	go k.AllowAccessNext(
   204  		orgChan,
   205  		r.URL.Path,
   206  		request.RealIP(r),
   207  		r,
   208  		&orgSessionCopy,
   209  	)
   210  
   211  	if found && !active.(bool) {
   212  		k.Logger().Debug("Is not active")
   213  		return errors.New("This organization access has been disabled or quota/rate limit is exceeded, please contact your API administrator"), http.StatusForbidden
   214  	}
   215  
   216  	// Request is valid, carry on
   217  	return nil, http.StatusOK
   218  }
   219  
   220  func (k *OrganizationMonitor) AllowAccessNext(
   221  	orgChan chan bool,
   222  	path string,
   223  	IP string,
   224  	r *http.Request,
   225  	session *user.SessionState) {
   226  
   227  	// Is it active?
   228  	logEntry := getExplicitLogEntryForRequest(k.Logger(), path, IP, k.Spec.OrgID, nil)
   229  	if session.IsInactive {
   230  		logEntry.Warning("Organisation access is disabled.")
   231  		orgChan <- false
   232  		return
   233  	}
   234  
   235  	// We found a session, apply the quota and rate limiter
   236  	reason := k.sessionlimiter.ForwardMessage(
   237  		r,
   238  		session,
   239  		k.Spec.OrgID,
   240  		k.Spec.OrgSessionManager.Store(),
   241  		session.Per > 0 && session.Rate > 0,
   242  		true,
   243  		&k.Spec.GlobalConfig,
   244  		k.Spec.APIID,
   245  		false,
   246  	)
   247  
   248  	sessionLifeTime := session.Lifetime(k.Spec.SessionLifetime)
   249  
   250  	if err := k.Spec.OrgSessionManager.UpdateSession(k.Spec.OrgID, session, sessionLifeTime, false); err == nil {
   251  		// update in-app cache if needed
   252  		if !k.Spec.GlobalConfig.LocalSessionCache.DisableCacheSessionState {
   253  			SessionCache.Set(k.Spec.OrgID, *session, time.Second*time.Duration(sessionLifeTime))
   254  		}
   255  	} else {
   256  		logEntry.WithError(err).WithField("orgID", k.Spec.OrgID).Error("Could not update org session")
   257  	}
   258  
   259  	isExceeded := false
   260  	switch reason {
   261  	case sessionFailNone:
   262  		// all good, keep org active
   263  	case sessionFailQuota:
   264  		isExceeded = true
   265  
   266  		logEntry.Warning("Organisation quota has been exceeded.")
   267  
   268  		// Fire a quota exceeded event
   269  		k.FireEvent(
   270  			EventOrgQuotaExceeded,
   271  			EventKeyFailureMeta{
   272  				EventMetaDefault: EventMetaDefault{
   273  					Message: "Organisation quota has been exceeded",
   274  				},
   275  				Path:   path,
   276  				Origin: IP,
   277  				Key:    k.Spec.OrgID,
   278  			},
   279  		)
   280  	case sessionFailRateLimit:
   281  		isExceeded = true
   282  
   283  		logEntry.Warning("Organisation rate limit has been exceeded.")
   284  
   285  		// Fire a rate limit exceeded event
   286  		k.FireEvent(
   287  			EventOrgRateLimitExceeded,
   288  			EventKeyFailureMeta{
   289  				EventMetaDefault: EventMetaDefault{
   290  					Message: "Organisation rate limit has been exceeded",
   291  				},
   292  				Path:   path,
   293  				Origin: IP,
   294  				Key:    k.Spec.OrgID,
   295  			},
   296  		)
   297  	}
   298  
   299  	if k.Spec.GlobalConfig.Monitor.MonitorOrgKeys {
   300  		// Run the trigger monitor
   301  		k.mon.Check(session, "")
   302  	}
   303  
   304  	if isExceeded {
   305  		orgChan <- false
   306  		return
   307  	}
   308  
   309  	orgChan <- true
   310  }