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 }