github.com/crowdsecurity/crowdsec@v1.6.1/pkg/database/decisions.go (about) 1 package database 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "time" 8 9 "entgo.io/ent/dialect/sql" 10 "github.com/pkg/errors" 11 12 "github.com/crowdsecurity/go-cs-lib/slicetools" 13 14 "github.com/crowdsecurity/crowdsec/pkg/database/ent" 15 "github.com/crowdsecurity/crowdsec/pkg/database/ent/decision" 16 "github.com/crowdsecurity/crowdsec/pkg/database/ent/predicate" 17 "github.com/crowdsecurity/crowdsec/pkg/types" 18 ) 19 20 type DecisionsByScenario struct { 21 Scenario string 22 Count int 23 Origin string 24 Type string 25 } 26 27 func BuildDecisionRequestWithFilter(query *ent.DecisionQuery, filter map[string][]string) (*ent.DecisionQuery, error) { 28 var err error 29 var start_ip, start_sfx, end_ip, end_sfx int64 30 var ip_sz int 31 var contains = true 32 /*if contains is true, return bans that *contains* the given value (value is the inner) 33 else, return bans that are *contained* by the given value (value is the outer)*/ 34 35 /*the simulated filter is a bit different : if it's not present *or* set to false, specifically exclude records with simulated to true */ 36 if v, ok := filter["simulated"]; ok { 37 if v[0] == "false" { 38 query = query.Where(decision.SimulatedEQ(false)) 39 } 40 delete(filter, "simulated") 41 } else { 42 query = query.Where(decision.SimulatedEQ(false)) 43 } 44 45 for param, value := range filter { 46 switch param { 47 case "contains": 48 contains, err = strconv.ParseBool(value[0]) 49 if err != nil { 50 return nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err) 51 } 52 case "scopes", "scope": //Swagger mentions both of them, let's just support both to make sure we don't break anything 53 scopes := strings.Split(value[0], ",") 54 for i, scope := range scopes { 55 switch strings.ToLower(scope) { 56 case "ip": 57 scopes[i] = types.Ip 58 case "range": 59 scopes[i] = types.Range 60 case "country": 61 scopes[i] = types.Country 62 case "as": 63 scopes[i] = types.AS 64 } 65 } 66 query = query.Where(decision.ScopeIn(scopes...)) 67 case "value": 68 query = query.Where(decision.ValueEQ(value[0])) 69 case "type": 70 query = query.Where(decision.TypeEQ(value[0])) 71 case "origins": 72 query = query.Where( 73 decision.OriginIn(strings.Split(value[0], ",")...), 74 ) 75 case "scenarios_containing": 76 predicates := decisionPredicatesFromStr(value[0], decision.ScenarioContainsFold) 77 query = query.Where(decision.Or(predicates...)) 78 case "scenarios_not_containing": 79 predicates := decisionPredicatesFromStr(value[0], decision.ScenarioContainsFold) 80 query = query.Where(decision.Not( 81 decision.Or( 82 predicates..., 83 ), 84 )) 85 case "ip", "range": 86 ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0]) 87 if err != nil { 88 return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err) 89 } 90 case "limit": 91 limit, err := strconv.Atoi(value[0]) 92 if err != nil { 93 return nil, errors.Wrapf(InvalidFilter, "invalid limit value : %s", err) 94 } 95 query = query.Limit(limit) 96 case "offset": 97 offset, err := strconv.Atoi(value[0]) 98 if err != nil { 99 return nil, errors.Wrapf(InvalidFilter, "invalid offset value : %s", err) 100 } 101 query = query.Offset(offset) 102 case "id_gt": 103 id, err := strconv.Atoi(value[0]) 104 if err != nil { 105 return nil, errors.Wrapf(InvalidFilter, "invalid id_gt value : %s", err) 106 } 107 query = query.Where(decision.IDGT(id)) 108 } 109 } 110 query, err = applyStartIpEndIpFilter(query, contains, ip_sz, start_ip, start_sfx, end_ip, end_sfx) 111 if err != nil { 112 return nil, fmt.Errorf("fail to apply StartIpEndIpFilter: %w", err) 113 } 114 return query, nil 115 } 116 func (c *Client) QueryAllDecisionsWithFilters(filters map[string][]string) ([]*ent.Decision, error) { 117 query := c.Ent.Decision.Query().Where( 118 decision.UntilGT(time.Now().UTC()), 119 ) 120 //Allow a bouncer to ask for non-deduplicated results 121 if v, ok := filters["dedup"]; !ok || v[0] != "false" { 122 query = query.Where(longestDecisionForScopeTypeValue) 123 } 124 125 query, err := BuildDecisionRequestWithFilter(query, filters) 126 127 if err != nil { 128 c.Log.Warningf("QueryAllDecisionsWithFilters : %s", err) 129 return []*ent.Decision{}, errors.Wrap(QueryFail, "get all decisions with filters") 130 } 131 132 query = query.Order(ent.Asc(decision.FieldID)) 133 134 data, err := query.All(c.CTX) 135 if err != nil { 136 c.Log.Warningf("QueryAllDecisionsWithFilters : %s", err) 137 return []*ent.Decision{}, errors.Wrap(QueryFail, "get all decisions with filters") 138 } 139 return data, nil 140 } 141 142 func (c *Client) QueryExpiredDecisionsWithFilters(filters map[string][]string) ([]*ent.Decision, error) { 143 query := c.Ent.Decision.Query().Where( 144 decision.UntilLT(time.Now().UTC()), 145 ) 146 //Allow a bouncer to ask for non-deduplicated results 147 if v, ok := filters["dedup"]; !ok || v[0] != "false" { 148 query = query.Where(longestDecisionForScopeTypeValue) 149 } 150 151 query, err := BuildDecisionRequestWithFilter(query, filters) 152 153 query = query.Order(ent.Asc(decision.FieldID)) 154 155 if err != nil { 156 c.Log.Warningf("QueryExpiredDecisionsWithFilters : %s", err) 157 return []*ent.Decision{}, errors.Wrap(QueryFail, "get expired decisions with filters") 158 } 159 data, err := query.All(c.CTX) 160 if err != nil { 161 c.Log.Warningf("QueryExpiredDecisionsWithFilters : %s", err) 162 return []*ent.Decision{}, errors.Wrap(QueryFail, "expired decisions") 163 } 164 return data, nil 165 } 166 167 func (c *Client) QueryDecisionCountByScenario(filters map[string][]string) ([]*DecisionsByScenario, error) { 168 query := c.Ent.Decision.Query().Where( 169 decision.UntilGT(time.Now().UTC()), 170 ) 171 query, err := BuildDecisionRequestWithFilter(query, filters) 172 173 if err != nil { 174 c.Log.Warningf("QueryDecisionCountByScenario : %s", err) 175 return nil, errors.Wrap(QueryFail, "count all decisions with filters") 176 } 177 178 var r []*DecisionsByScenario 179 180 err = query.GroupBy(decision.FieldScenario, decision.FieldOrigin, decision.FieldType).Aggregate(ent.Count()).Scan(c.CTX, &r) 181 182 if err != nil { 183 c.Log.Warningf("QueryDecisionCountByScenario : %s", err) 184 return nil, errors.Wrap(QueryFail, "count all decisions with filters") 185 } 186 187 return r, nil 188 } 189 190 func (c *Client) QueryDecisionWithFilter(filter map[string][]string) ([]*ent.Decision, error) { 191 var data []*ent.Decision 192 var err error 193 194 decisions := c.Ent.Decision.Query(). 195 Where(decision.UntilGTE(time.Now().UTC())) 196 197 decisions, err = BuildDecisionRequestWithFilter(decisions, filter) 198 if err != nil { 199 return []*ent.Decision{}, err 200 } 201 202 err = decisions.Select( 203 decision.FieldID, 204 decision.FieldUntil, 205 decision.FieldScenario, 206 decision.FieldType, 207 decision.FieldStartIP, 208 decision.FieldEndIP, 209 decision.FieldValue, 210 decision.FieldScope, 211 decision.FieldOrigin, 212 ).Scan(c.CTX, &data) 213 if err != nil { 214 c.Log.Warningf("QueryDecisionWithFilter : %s", err) 215 return []*ent.Decision{}, errors.Wrap(QueryFail, "query decision failed") 216 } 217 218 return data, nil 219 } 220 221 // ent translation of https://stackoverflow.com/a/28090544 222 func longestDecisionForScopeTypeValue(s *sql.Selector) { 223 t := sql.Table(decision.Table) 224 s.LeftJoin(t).OnP(sql.And( 225 sql.ColumnsEQ( 226 t.C(decision.FieldValue), 227 s.C(decision.FieldValue), 228 ), 229 sql.ColumnsEQ( 230 t.C(decision.FieldType), 231 s.C(decision.FieldType), 232 ), 233 sql.ColumnsEQ( 234 t.C(decision.FieldScope), 235 s.C(decision.FieldScope), 236 ), 237 sql.ColumnsGT( 238 t.C(decision.FieldUntil), 239 s.C(decision.FieldUntil), 240 ), 241 )) 242 s.Where( 243 sql.IsNull( 244 t.C(decision.FieldUntil), 245 ), 246 ) 247 } 248 249 func (c *Client) QueryExpiredDecisionsSinceWithFilters(since time.Time, filters map[string][]string) ([]*ent.Decision, error) { 250 query := c.Ent.Decision.Query().Where( 251 decision.UntilLT(time.Now().UTC()), 252 decision.UntilGT(since), 253 ) 254 //Allow a bouncer to ask for non-deduplicated results 255 if v, ok := filters["dedup"]; !ok || v[0] != "false" { 256 query = query.Where(longestDecisionForScopeTypeValue) 257 } 258 query, err := BuildDecisionRequestWithFilter(query, filters) 259 if err != nil { 260 c.Log.Warningf("QueryExpiredDecisionsSinceWithFilters : %s", err) 261 return []*ent.Decision{}, errors.Wrap(QueryFail, "expired decisions with filters") 262 } 263 264 query = query.Order(ent.Asc(decision.FieldID)) 265 266 data, err := query.All(c.CTX) 267 if err != nil { 268 c.Log.Warningf("QueryExpiredDecisionsSinceWithFilters : %s", err) 269 return []*ent.Decision{}, errors.Wrap(QueryFail, "expired decisions with filters") 270 } 271 272 return data, nil 273 } 274 275 func (c *Client) QueryNewDecisionsSinceWithFilters(since time.Time, filters map[string][]string) ([]*ent.Decision, error) { 276 query := c.Ent.Decision.Query().Where( 277 decision.CreatedAtGT(since), 278 decision.UntilGT(time.Now().UTC()), 279 ) 280 //Allow a bouncer to ask for non-deduplicated results 281 if v, ok := filters["dedup"]; !ok || v[0] != "false" { 282 query = query.Where(longestDecisionForScopeTypeValue) 283 } 284 query, err := BuildDecisionRequestWithFilter(query, filters) 285 if err != nil { 286 c.Log.Warningf("QueryNewDecisionsSinceWithFilters : %s", err) 287 return []*ent.Decision{}, errors.Wrapf(QueryFail, "new decisions since '%s'", since.String()) 288 } 289 290 query = query.Order(ent.Asc(decision.FieldID)) 291 292 data, err := query.All(c.CTX) 293 if err != nil { 294 c.Log.Warningf("QueryNewDecisionsSinceWithFilters : %s", err) 295 return []*ent.Decision{}, errors.Wrapf(QueryFail, "new decisions since '%s'", since.String()) 296 } 297 return data, nil 298 } 299 300 func (c *Client) DeleteDecisionById(decisionId int) ([]*ent.Decision, error) { 301 toDelete, err := c.Ent.Decision.Query().Where(decision.IDEQ(decisionId)).All(c.CTX) 302 if err != nil { 303 c.Log.Warningf("DeleteDecisionById : %s", err) 304 return nil, errors.Wrapf(DeleteFail, "decision with id '%d' doesn't exist", decisionId) 305 } 306 count, err := c.BulkDeleteDecisions(toDelete, false) 307 c.Log.Debugf("deleted %d decisions", count) 308 return toDelete, err 309 } 310 311 func (c *Client) DeleteDecisionsWithFilter(filter map[string][]string) (string, []*ent.Decision, error) { 312 var err error 313 var start_ip, start_sfx, end_ip, end_sfx int64 314 var ip_sz int 315 var contains = true 316 /*if contains is true, return bans that *contains* the given value (value is the inner) 317 else, return bans that are *contained* by the given value (value is the outer) */ 318 319 decisions := c.Ent.Decision.Query() 320 for param, value := range filter { 321 switch param { 322 case "contains": 323 contains, err = strconv.ParseBool(value[0]) 324 if err != nil { 325 return "0", nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err) 326 } 327 case "scope": 328 decisions = decisions.Where(decision.ScopeEQ(value[0])) 329 case "value": 330 decisions = decisions.Where(decision.ValueEQ(value[0])) 331 case "type": 332 decisions = decisions.Where(decision.TypeEQ(value[0])) 333 case "ip", "range": 334 ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0]) 335 if err != nil { 336 return "0", nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err) 337 } 338 case "scenario": 339 decisions = decisions.Where(decision.ScenarioEQ(value[0])) 340 default: 341 return "0", nil, errors.Wrap(InvalidFilter, fmt.Sprintf("'%s' doesn't exist", param)) 342 } 343 } 344 345 if ip_sz == 4 { 346 if contains { /*decision contains {start_ip,end_ip}*/ 347 decisions = decisions.Where(decision.And( 348 decision.StartIPLTE(start_ip), 349 decision.EndIPGTE(end_ip), 350 decision.IPSizeEQ(int64(ip_sz)), 351 )) 352 } else { /*decision is contained within {start_ip,end_ip}*/ 353 decisions = decisions.Where(decision.And( 354 decision.StartIPGTE(start_ip), 355 decision.EndIPLTE(end_ip), 356 decision.IPSizeEQ(int64(ip_sz)), 357 )) 358 } 359 } else if ip_sz == 16 { 360 if contains { /*decision contains {start_ip,end_ip}*/ 361 decisions = decisions.Where(decision.And( 362 //matching addr size 363 decision.IPSizeEQ(int64(ip_sz)), 364 decision.Or( 365 //decision.start_ip < query.start_ip 366 decision.StartIPLT(start_ip), 367 decision.And( 368 //decision.start_ip == query.start_ip 369 decision.StartIPEQ(start_ip), 370 //decision.start_suffix <= query.start_suffix 371 decision.StartSuffixLTE(start_sfx), 372 )), 373 decision.Or( 374 //decision.end_ip > query.end_ip 375 decision.EndIPGT(end_ip), 376 decision.And( 377 //decision.end_ip == query.end_ip 378 decision.EndIPEQ(end_ip), 379 //decision.end_suffix >= query.end_suffix 380 decision.EndSuffixGTE(end_sfx), 381 ), 382 ), 383 )) 384 } else { 385 decisions = decisions.Where(decision.And( 386 //matching addr size 387 decision.IPSizeEQ(int64(ip_sz)), 388 decision.Or( 389 //decision.start_ip > query.start_ip 390 decision.StartIPGT(start_ip), 391 decision.And( 392 //decision.start_ip == query.start_ip 393 decision.StartIPEQ(start_ip), 394 //decision.start_suffix >= query.start_suffix 395 decision.StartSuffixGTE(start_sfx), 396 )), 397 decision.Or( 398 //decision.end_ip < query.end_ip 399 decision.EndIPLT(end_ip), 400 decision.And( 401 //decision.end_ip == query.end_ip 402 decision.EndIPEQ(end_ip), 403 //decision.end_suffix <= query.end_suffix 404 decision.EndSuffixLTE(end_sfx), 405 ), 406 ), 407 )) 408 } 409 } else if ip_sz != 0 { 410 return "0", nil, errors.Wrapf(InvalidFilter, "Unknown ip size %d", ip_sz) 411 } 412 413 toDelete, err := decisions.All(c.CTX) 414 if err != nil { 415 c.Log.Warningf("DeleteDecisionsWithFilter : %s", err) 416 return "0", nil, errors.Wrap(DeleteFail, "decisions with provided filter") 417 } 418 count, err := c.BulkDeleteDecisions(toDelete, false) 419 if err != nil { 420 c.Log.Warningf("While deleting decisions : %s", err) 421 return "0", nil, errors.Wrap(DeleteFail, "decisions with provided filter") 422 } 423 return strconv.Itoa(count), toDelete, nil 424 } 425 426 // SoftDeleteDecisionsWithFilter updates the expiration time to now() for the decisions matching the filter, and returns the updated items 427 func (c *Client) SoftDeleteDecisionsWithFilter(filter map[string][]string) (string, []*ent.Decision, error) { 428 var err error 429 var start_ip, start_sfx, end_ip, end_sfx int64 430 var ip_sz int 431 var contains = true 432 /*if contains is true, return bans that *contains* the given value (value is the inner) 433 else, return bans that are *contained* by the given value (value is the outer)*/ 434 decisions := c.Ent.Decision.Query().Where(decision.UntilGT(time.Now().UTC())) 435 for param, value := range filter { 436 switch param { 437 case "contains": 438 contains, err = strconv.ParseBool(value[0]) 439 if err != nil { 440 return "0", nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err) 441 } 442 case "scopes": 443 decisions = decisions.Where(decision.ScopeEQ(value[0])) 444 case "uuid": 445 decisions = decisions.Where(decision.UUIDIn(value...)) 446 case "origin": 447 decisions = decisions.Where(decision.OriginEQ(value[0])) 448 case "value": 449 decisions = decisions.Where(decision.ValueEQ(value[0])) 450 case "type": 451 decisions = decisions.Where(decision.TypeEQ(value[0])) 452 case "ip", "range": 453 ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0]) 454 if err != nil { 455 return "0", nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err) 456 } 457 case "scenario": 458 decisions = decisions.Where(decision.ScenarioEQ(value[0])) 459 default: 460 return "0", nil, errors.Wrapf(InvalidFilter, "'%s' doesn't exist", param) 461 } 462 } 463 if ip_sz == 4 { 464 if contains { 465 /*Decision contains {start_ip,end_ip}*/ 466 decisions = decisions.Where(decision.And( 467 decision.StartIPLTE(start_ip), 468 decision.EndIPGTE(end_ip), 469 decision.IPSizeEQ(int64(ip_sz)), 470 )) 471 } else { 472 /*Decision is contained within {start_ip,end_ip}*/ 473 decisions = decisions.Where(decision.And( 474 decision.StartIPGTE(start_ip), 475 decision.EndIPLTE(end_ip), 476 decision.IPSizeEQ(int64(ip_sz)), 477 )) 478 } 479 } else if ip_sz == 16 { 480 /*decision contains {start_ip,end_ip}*/ 481 if contains { 482 decisions = decisions.Where(decision.And( 483 //matching addr size 484 decision.IPSizeEQ(int64(ip_sz)), 485 decision.Or( 486 //decision.start_ip < query.start_ip 487 decision.StartIPLT(start_ip), 488 decision.And( 489 //decision.start_ip == query.start_ip 490 decision.StartIPEQ(start_ip), 491 //decision.start_suffix <= query.start_suffix 492 decision.StartSuffixLTE(start_sfx), 493 )), 494 decision.Or( 495 //decision.end_ip > query.end_ip 496 decision.EndIPGT(end_ip), 497 decision.And( 498 //decision.end_ip == query.end_ip 499 decision.EndIPEQ(end_ip), 500 //decision.end_suffix >= query.end_suffix 501 decision.EndSuffixGTE(end_sfx), 502 ), 503 ), 504 )) 505 } else { 506 /*decision is contained within {start_ip,end_ip}*/ 507 decisions = decisions.Where(decision.And( 508 //matching addr size 509 decision.IPSizeEQ(int64(ip_sz)), 510 decision.Or( 511 //decision.start_ip > query.start_ip 512 decision.StartIPGT(start_ip), 513 decision.And( 514 //decision.start_ip == query.start_ip 515 decision.StartIPEQ(start_ip), 516 //decision.start_suffix >= query.start_suffix 517 decision.StartSuffixGTE(start_sfx), 518 )), 519 decision.Or( 520 //decision.end_ip < query.end_ip 521 decision.EndIPLT(end_ip), 522 decision.And( 523 //decision.end_ip == query.end_ip 524 decision.EndIPEQ(end_ip), 525 //decision.end_suffix <= query.end_suffix 526 decision.EndSuffixLTE(end_sfx), 527 ), 528 ), 529 )) 530 } 531 } else if ip_sz != 0 { 532 return "0", nil, errors.Wrapf(InvalidFilter, "Unknown ip size %d", ip_sz) 533 } 534 DecisionsToDelete, err := decisions.All(c.CTX) 535 if err != nil { 536 c.Log.Warningf("SoftDeleteDecisionsWithFilter : %s", err) 537 return "0", nil, errors.Wrap(DeleteFail, "soft delete decisions with provided filter") 538 } 539 540 count, err := c.BulkDeleteDecisions(DecisionsToDelete, true) 541 if err != nil { 542 return "0", nil, errors.Wrapf(DeleteFail, "soft delete decisions with provided filter : %s", err) 543 } 544 return strconv.Itoa(count), DecisionsToDelete, err 545 } 546 547 // BulkDeleteDecisions set the expiration of a bulk of decisions to now() or hard deletes them. 548 // We are doing it this way so we can return impacted decisions for sync with CAPI/PAPI 549 func (c *Client) BulkDeleteDecisions(decisionsToDelete []*ent.Decision, softDelete bool) (int, error) { 550 const bulkSize = 256 //scientifically proven to be the best value for bulk delete 551 552 var ( 553 nbUpdates int 554 err error 555 totalUpdates = 0 556 ) 557 558 idsToDelete := make([]int, len(decisionsToDelete)) 559 for i, decision := range decisionsToDelete { 560 idsToDelete[i] = decision.ID 561 } 562 563 for _, chunk := range slicetools.Chunks(idsToDelete, bulkSize) { 564 if softDelete { 565 nbUpdates, err = c.Ent.Decision.Update().Where( 566 decision.IDIn(chunk...), 567 ).SetUntil(time.Now().UTC()).Save(c.CTX) 568 if err != nil { 569 return totalUpdates, fmt.Errorf("soft delete decisions with provided filter: %w", err) 570 } 571 } else { 572 nbUpdates, err = c.Ent.Decision.Delete().Where( 573 decision.IDIn(chunk...), 574 ).Exec(c.CTX) 575 if err != nil { 576 return totalUpdates, fmt.Errorf("hard delete decisions with provided filter: %w", err) 577 } 578 } 579 totalUpdates += nbUpdates 580 } 581 582 return totalUpdates, nil 583 } 584 585 // SoftDeleteDecisionByID set the expiration of a decision to now() 586 func (c *Client) SoftDeleteDecisionByID(decisionID int) (int, []*ent.Decision, error) { 587 toUpdate, err := c.Ent.Decision.Query().Where(decision.IDEQ(decisionID)).All(c.CTX) 588 589 // XXX: do we want 500 or 404 here? 590 if err != nil || len(toUpdate) == 0 { 591 c.Log.Warningf("SoftDeleteDecisionByID : %v (nb soft deleted: %d)", err, len(toUpdate)) 592 return 0, nil, errors.Wrapf(DeleteFail, "decision with id '%d' doesn't exist", decisionID) 593 } 594 595 if len(toUpdate) == 0 { 596 return 0, nil, ItemNotFound 597 } 598 599 count, err := c.BulkDeleteDecisions(toUpdate, true) 600 return count, toUpdate, err 601 } 602 603 func (c *Client) CountDecisionsByValue(decisionValue string) (int, error) { 604 var err error 605 var start_ip, start_sfx, end_ip, end_sfx int64 606 var ip_sz, count int 607 ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(decisionValue) 608 609 if err != nil { 610 return 0, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", decisionValue, err) 611 } 612 613 contains := true 614 decisions := c.Ent.Decision.Query() 615 decisions, err = applyStartIpEndIpFilter(decisions, contains, ip_sz, start_ip, start_sfx, end_ip, end_sfx) 616 if err != nil { 617 return 0, errors.Wrapf(err, "fail to apply StartIpEndIpFilter") 618 } 619 620 count, err = decisions.Count(c.CTX) 621 if err != nil { 622 return 0, errors.Wrapf(err, "fail to count decisions") 623 } 624 625 return count, nil 626 } 627 628 func (c *Client) CountDecisionsSinceByValue(decisionValue string, since time.Time) (int, error) { 629 ip_sz, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(decisionValue) 630 631 if err != nil { 632 return 0, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", decisionValue, err) 633 } 634 635 contains := true 636 decisions := c.Ent.Decision.Query().Where( 637 decision.CreatedAtGT(since), 638 ) 639 640 decisions, err = applyStartIpEndIpFilter(decisions, contains, ip_sz, start_ip, start_sfx, end_ip, end_sfx) 641 if err != nil { 642 return 0, errors.Wrapf(err, "fail to apply StartIpEndIpFilter") 643 } 644 645 count, err := decisions.Count(c.CTX) 646 if err != nil { 647 return 0, errors.Wrapf(err, "fail to count decisions") 648 } 649 650 return count, nil 651 } 652 653 func applyStartIpEndIpFilter(decisions *ent.DecisionQuery, contains bool, ip_sz int, start_ip int64, start_sfx int64, end_ip int64, end_sfx int64) (*ent.DecisionQuery, error) { 654 if ip_sz == 4 { 655 if contains { 656 /*Decision contains {start_ip,end_ip}*/ 657 decisions = decisions.Where(decision.And( 658 decision.StartIPLTE(start_ip), 659 decision.EndIPGTE(end_ip), 660 decision.IPSizeEQ(int64(ip_sz)), 661 )) 662 } else { 663 /*Decision is contained within {start_ip,end_ip}*/ 664 decisions = decisions.Where(decision.And( 665 decision.StartIPGTE(start_ip), 666 decision.EndIPLTE(end_ip), 667 decision.IPSizeEQ(int64(ip_sz)), 668 )) 669 } 670 return decisions, nil 671 } 672 673 if ip_sz == 16 { 674 /*decision contains {start_ip,end_ip}*/ 675 if contains { 676 decisions = decisions.Where(decision.And( 677 //matching addr size 678 decision.IPSizeEQ(int64(ip_sz)), 679 decision.Or( 680 //decision.start_ip < query.start_ip 681 decision.StartIPLT(start_ip), 682 decision.And( 683 //decision.start_ip == query.start_ip 684 decision.StartIPEQ(start_ip), 685 //decision.start_suffix <= query.start_suffix 686 decision.StartSuffixLTE(start_sfx), 687 )), 688 decision.Or( 689 //decision.end_ip > query.end_ip 690 decision.EndIPGT(end_ip), 691 decision.And( 692 //decision.end_ip == query.end_ip 693 decision.EndIPEQ(end_ip), 694 //decision.end_suffix >= query.end_suffix 695 decision.EndSuffixGTE(end_sfx), 696 ), 697 ), 698 )) 699 } else { 700 /*decision is contained within {start_ip,end_ip}*/ 701 decisions = decisions.Where(decision.And( 702 //matching addr size 703 decision.IPSizeEQ(int64(ip_sz)), 704 decision.Or( 705 //decision.start_ip > query.start_ip 706 decision.StartIPGT(start_ip), 707 decision.And( 708 //decision.start_ip == query.start_ip 709 decision.StartIPEQ(start_ip), 710 //decision.start_suffix >= query.start_suffix 711 decision.StartSuffixGTE(start_sfx), 712 )), 713 decision.Or( 714 //decision.end_ip < query.end_ip 715 decision.EndIPLT(end_ip), 716 decision.And( 717 //decision.end_ip == query.end_ip 718 decision.EndIPEQ(end_ip), 719 //decision.end_suffix <= query.end_suffix 720 decision.EndSuffixLTE(end_sfx), 721 ), 722 ), 723 )) 724 } 725 return decisions, nil 726 } 727 728 if ip_sz != 0 { 729 return nil, errors.Wrapf(InvalidFilter, "unknown ip size %d", ip_sz) 730 } 731 732 return decisions, nil 733 } 734 735 func decisionPredicatesFromStr(s string, predicateFunc func(string) predicate.Decision) []predicate.Decision { 736 words := strings.Split(s, ",") 737 predicates := make([]predicate.Decision, len(words)) 738 for i, word := range words { 739 predicates[i] = predicateFunc(word) 740 } 741 return predicates 742 }