Files
hetty/pkg/proj/proj.go

319 lines
8.4 KiB
Go
Raw Normal View History

2020-10-11 17:09:39 +02:00
package proj
import (
2020-10-29 20:54:17 +01:00
"context"
2020-10-11 17:09:39 +02:00
"errors"
"fmt"
2022-01-21 11:45:54 +01:00
"math/rand"
2020-10-11 17:09:39 +02:00
"regexp"
2020-10-29 20:54:17 +01:00
"sync"
2022-01-21 11:45:54 +01:00
"time"
"github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/filter"
2022-03-23 14:31:27 +01:00
"github.com/dstotijn/hetty/pkg/proxy/intercept"
2022-01-21 11:45:54 +01:00
"github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope"
2022-02-22 14:10:39 +01:00
"github.com/dstotijn/hetty/pkg/sender"
2020-10-11 17:09:39 +02:00
)
2022-01-21 11:45:54 +01:00
//nolint:gosec
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
2020-10-11 17:09:39 +02:00
// Service is used for managing projects.
2022-01-01 16:11:49 +01:00
type Service interface {
2022-01-21 11:45:54 +01:00
CreateProject(ctx context.Context, name string) (Project, error)
OpenProject(ctx context.Context, projectID ulid.ULID) (Project, error)
CloseProject() error
DeleteProject(ctx context.Context, projectID ulid.ULID) error
ActiveProject(ctx context.Context) (Project, error)
IsProjectActive(projectID ulid.ULID) bool
Projects(ctx context.Context) ([]Project, error)
Scope() *scope.Scope
SetScopeRules(ctx context.Context, rules []scope.Rule) error
SetRequestLogFindFilter(ctx context.Context, filter reqlog.FindRequestsFilter) error
2022-02-22 14:10:39 +01:00
SetSenderRequestFindFilter(ctx context.Context, filter sender.FindRequestsFilter) error
2022-03-23 14:31:27 +01:00
UpdateInterceptSettings(ctx context.Context, settings intercept.Settings) error
2022-01-01 16:11:49 +01:00
}
type service struct {
repo Repository
2022-03-23 14:31:27 +01:00
interceptSvc *intercept.Service
reqLogSvc reqlog.Service
senderSvc sender.Service
scope *scope.Scope
activeProjectID ulid.ULID
mu sync.RWMutex
2020-10-11 17:09:39 +02:00
}
type Project struct {
2022-01-21 11:45:54 +01:00
ID ulid.ULID
2020-10-11 17:09:39 +02:00
Name string
2022-01-21 11:45:54 +01:00
Settings Settings
isActive bool
}
type Settings struct {
2022-03-23 14:31:27 +01:00
// Request log settings
2022-01-21 11:45:54 +01:00
ReqLogBypassOutOfScope bool
ReqLogOnlyFindInScope bool
ReqLogSearchExpr filter.Expression
2022-02-22 14:10:39 +01:00
2022-03-23 14:31:27 +01:00
// Intercept settings
InterceptRequests bool
InterceptResponses bool
InterceptRequestFilter filter.Expression
InterceptResponseFilter filter.Expression
2022-03-23 14:31:27 +01:00
// Sender settings
2022-02-22 14:10:39 +01:00
SenderOnlyFindInScope bool
SenderSearchExpr filter.Expression
2022-02-22 14:10:39 +01:00
2022-03-23 14:31:27 +01:00
// Scope settings
2022-02-22 14:10:39 +01:00
ScopeRules []scope.Rule
2020-10-11 17:09:39 +02:00
}
var (
2022-01-21 11:45:54 +01:00
ErrProjectNotFound = errors.New("proj: project not found")
ErrNoProject = errors.New("proj: no open project")
ErrNoSettings = errors.New("proj: settings not found")
ErrInvalidName = errors.New("proj: invalid name, must be alphanumeric or whitespace chars")
2020-10-11 17:09:39 +02:00
)
var nameRegexp = regexp.MustCompile(`^[\w\d\s]+$`)
2022-01-21 11:45:54 +01:00
type Config struct {
2022-03-23 14:31:27 +01:00
Repository Repository
InterceptService *intercept.Service
ReqLogService reqlog.Service
SenderService sender.Service
Scope *scope.Scope
2022-01-21 11:45:54 +01:00
}
2020-10-11 17:09:39 +02:00
// NewService returns a new Service.
2022-01-21 11:45:54 +01:00
func NewService(cfg Config) (Service, error) {
2022-01-01 16:11:49 +01:00
return &service{
2022-03-23 14:31:27 +01:00
repo: cfg.Repository,
interceptSvc: cfg.InterceptService,
reqLogSvc: cfg.ReqLogService,
senderSvc: cfg.SenderService,
scope: cfg.Scope,
2020-10-11 17:09:39 +02:00
}, nil
}
2022-01-21 11:45:54 +01:00
func (svc *service) CreateProject(ctx context.Context, name string) (Project, error) {
if !nameRegexp.MatchString(name) {
return Project{}, ErrInvalidName
}
project := Project{
ID: ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy),
Name: name,
}
err := svc.repo.UpsertProject(ctx, project)
if err != nil {
return Project{}, fmt.Errorf("proj: could not create project: %w", err)
}
return project, nil
}
// CloseProject closes the currently open project (if there is one).
func (svc *service) CloseProject() error {
2020-10-29 20:54:17 +01:00
svc.mu.Lock()
defer svc.mu.Unlock()
2022-01-21 11:45:54 +01:00
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
return nil
2020-10-11 17:09:39 +02:00
}
2020-10-29 20:54:17 +01:00
2022-01-21 11:45:54 +01:00
svc.activeProjectID = ulid.ULID{}
2022-02-22 14:10:39 +01:00
svc.reqLogSvc.SetActiveProjectID(ulid.ULID{})
svc.reqLogSvc.SetBypassOutOfScopeRequests(false)
svc.reqLogSvc.SetFindReqsFilter(reqlog.FindRequestsFilter{})
2022-03-23 14:31:27 +01:00
svc.interceptSvc.UpdateSettings(intercept.Settings{
RequestsEnabled: false,
ResponsesEnabled: false,
RequestFilter: nil,
ResponseFilter: nil,
})
2022-02-22 14:10:39 +01:00
svc.senderSvc.SetActiveProjectID(ulid.ULID{})
svc.senderSvc.SetFindReqsFilter(sender.FindRequestsFilter{})
2022-01-21 11:45:54 +01:00
svc.scope.SetRules(nil)
2020-10-11 17:09:39 +02:00
return nil
}
2022-01-21 11:45:54 +01:00
// DeleteProject removes a project from the repository.
func (svc *service) DeleteProject(ctx context.Context, projectID ulid.ULID) error {
if svc.activeProjectID.Compare(projectID) == 0 {
return fmt.Errorf("proj: project (%v) is active", projectID.String())
2020-10-11 17:09:39 +02:00
}
2022-01-21 11:45:54 +01:00
if err := svc.repo.DeleteProject(ctx, projectID); err != nil {
2021-04-25 16:23:53 +02:00
return fmt.Errorf("proj: could not delete project: %w", err)
2020-10-11 17:09:39 +02:00
}
return nil
}
2022-01-21 11:45:54 +01:00
// OpenProject sets a project as the currently active project.
func (svc *service) OpenProject(ctx context.Context, projectID ulid.ULID) (Project, error) {
2020-10-29 20:54:17 +01:00
svc.mu.Lock()
defer svc.mu.Unlock()
2022-01-21 11:45:54 +01:00
project, err := svc.repo.FindProjectByID(ctx, projectID)
if err != nil {
return Project{}, fmt.Errorf("proj: failed to get project: %w", err)
2020-10-11 17:09:39 +02:00
}
2022-01-21 11:45:54 +01:00
svc.activeProjectID = project.ID
2022-02-22 14:10:39 +01:00
2022-03-23 14:31:27 +01:00
// Request log settings.
2022-02-22 14:10:39 +01:00
svc.reqLogSvc.SetFindReqsFilter(reqlog.FindRequestsFilter{
2022-01-21 11:45:54 +01:00
ProjectID: project.ID,
OnlyInScope: project.Settings.ReqLogOnlyFindInScope,
2022-02-22 14:10:39 +01:00
SearchExpr: project.Settings.ReqLogSearchExpr,
})
svc.reqLogSvc.SetBypassOutOfScopeRequests(project.Settings.ReqLogBypassOutOfScope)
svc.reqLogSvc.SetActiveProjectID(project.ID)
2022-03-23 14:31:27 +01:00
// Intercept settings.
svc.interceptSvc.UpdateSettings(intercept.Settings{
RequestsEnabled: project.Settings.InterceptRequests,
ResponsesEnabled: project.Settings.InterceptResponses,
RequestFilter: project.Settings.InterceptRequestFilter,
ResponseFilter: project.Settings.InterceptResponseFilter,
})
// Sender settings.
2022-02-22 14:10:39 +01:00
svc.senderSvc.SetActiveProjectID(project.ID)
svc.senderSvc.SetFindReqsFilter(sender.FindRequestsFilter{
ProjectID: project.ID,
OnlyInScope: project.Settings.SenderOnlyFindInScope,
SearchExpr: project.Settings.SenderSearchExpr,
})
2022-01-21 11:45:54 +01:00
2022-03-23 14:31:27 +01:00
// Scope settings.
2022-01-21 11:45:54 +01:00
svc.scope.SetRules(project.Settings.ScopeRules)
2020-10-11 17:09:39 +02:00
2022-01-21 11:45:54 +01:00
return project, nil
2020-10-11 17:09:39 +02:00
}
2022-01-21 11:45:54 +01:00
func (svc *service) ActiveProject(ctx context.Context) (Project, error) {
activeProjectID := svc.activeProjectID
if activeProjectID.Compare(ulid.ULID{}) == 0 {
2020-10-11 17:09:39 +02:00
return Project{}, ErrNoProject
}
2022-01-21 11:45:54 +01:00
project, err := svc.repo.FindProjectByID(ctx, activeProjectID)
if err != nil {
return Project{}, fmt.Errorf("proj: failed to get active project: %w", err)
}
project.isActive = true
return project, nil
2020-10-11 17:09:39 +02:00
}
2022-01-21 11:45:54 +01:00
func (svc *service) Projects(ctx context.Context) ([]Project, error) {
projects, err := svc.repo.Projects(ctx)
2020-10-11 17:09:39 +02:00
if err != nil {
2021-04-25 16:23:53 +02:00
return nil, fmt.Errorf("proj: could not get projects: %w", err)
2020-10-11 17:09:39 +02:00
}
2020-10-29 20:54:17 +01:00
return projects, nil
}
2022-01-21 11:45:54 +01:00
func (svc *service) Scope() *scope.Scope {
return svc.scope
}
func (svc *service) SetScopeRules(ctx context.Context, rules []scope.Rule) error {
project, err := svc.ActiveProject(ctx)
if err != nil {
return err
}
project.Settings.ScopeRules = rules
err = svc.repo.UpsertProject(ctx, project)
if err != nil {
return fmt.Errorf("proj: failed to update project: %w", err)
}
svc.scope.SetRules(rules)
return nil
}
func (svc *service) SetRequestLogFindFilter(ctx context.Context, filter reqlog.FindRequestsFilter) error {
project, err := svc.ActiveProject(ctx)
if err != nil {
return err
}
filter.ProjectID = project.ID
project.Settings.ReqLogOnlyFindInScope = filter.OnlyInScope
2022-02-22 14:10:39 +01:00
project.Settings.ReqLogSearchExpr = filter.SearchExpr
err = svc.repo.UpsertProject(ctx, project)
if err != nil {
return fmt.Errorf("proj: failed to update project: %w", err)
}
svc.reqLogSvc.SetFindReqsFilter(filter)
return nil
}
func (svc *service) SetSenderRequestFindFilter(ctx context.Context, filter sender.FindRequestsFilter) error {
project, err := svc.ActiveProject(ctx)
if err != nil {
return err
}
filter.ProjectID = project.ID
project.Settings.SenderOnlyFindInScope = filter.OnlyInScope
project.Settings.SenderSearchExpr = filter.SearchExpr
2022-01-21 11:45:54 +01:00
err = svc.repo.UpsertProject(ctx, project)
if err != nil {
return fmt.Errorf("proj: failed to update project: %w", err)
}
2022-02-22 14:10:39 +01:00
svc.senderSvc.SetFindReqsFilter(filter)
2022-01-21 11:45:54 +01:00
return nil
}
func (svc *service) IsProjectActive(projectID ulid.ULID) bool {
return projectID.Compare(svc.activeProjectID) == 0
}
2022-03-23 14:31:27 +01:00
func (svc *service) UpdateInterceptSettings(ctx context.Context, settings intercept.Settings) error {
project, err := svc.ActiveProject(ctx)
if err != nil {
return err
}
project.Settings.InterceptRequests = settings.RequestsEnabled
project.Settings.InterceptResponses = settings.ResponsesEnabled
project.Settings.InterceptRequestFilter = settings.RequestFilter
project.Settings.InterceptResponseFilter = settings.ResponseFilter
err = svc.repo.UpsertProject(ctx, project)
if err != nil {
return fmt.Errorf("proj: failed to update project: %w", err)
}
svc.interceptSvc.UpdateSettings(settings)
return nil
}