From 5ff3066d6d1831c6b3ed0a944221bcbe8cad2d10 Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Mon, 20 Jan 2025 22:38:27 -0500 Subject: [PATCH] WIP cycle --- pkg/calls/call.go | 5 +- pkg/database/incidents.sql.go | 15 ++ pkg/database/models.go | 226 ++++++++++++++--------------- pkg/database/querier.go | 1 + pkg/incidents/incident.go | 1 - pkg/incidents/incstore/rbac.go | 65 +++++++++ pkg/incidents/incstore/store.go | 9 ++ pkg/rbac/conditions.go | 11 +- pkg/rbac/entities.go | 23 +++ pkg/rbac/policy.go | 34 ++++- pkg/rbac/rbac.go | 39 +---- pkg/server/routes.go | 1 - pkg/talkgroups/talkgroup.go | 7 +- pkg/talkgroups/tgstore/store.go | 2 +- pkg/users/guest.go | 13 -- sql/postgres/queries/incidents.sql | 7 + 16 files changed, 287 insertions(+), 172 deletions(-) create mode 100644 pkg/incidents/incstore/rbac.go create mode 100644 pkg/rbac/entities.go delete mode 100644 pkg/users/guest.go diff --git a/pkg/calls/call.go b/pkg/calls/call.go index 1f0e338..8511294 100644 --- a/pkg/calls/call.go +++ b/pkg/calls/call.go @@ -8,7 +8,6 @@ import ( "dynatron.me/x/stillbox/internal/audio" "dynatron.me/x/stillbox/internal/jsontypes" "dynatron.me/x/stillbox/pkg/pb" - "dynatron.me/x/stillbox/pkg/rbac" "dynatron.me/x/stillbox/pkg/talkgroups" "dynatron.me/x/stillbox/pkg/users" @@ -16,6 +15,8 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) +const Resource = "call" + type CallDuration time.Duration func (d CallDuration) Duration() time.Duration { @@ -76,7 +77,7 @@ type Call struct { } func (c *Call) GetResourceName() string { - return rbac.ResourceCall + return Resource } func (c *Call) String() string { diff --git a/pkg/database/incidents.sql.go b/pkg/database/incidents.sql.go index c39e6ca..b233955 100644 --- a/pkg/database/incidents.sql.go +++ b/pkg/database/incidents.sql.go @@ -40,6 +40,21 @@ func (q *Queries) AddToIncident(ctx context.Context, incidentID uuid.UUID, callI return err } +const callInIncident = `-- name: CallInIncident :one +SELECT EXISTS + (SELECT 1 FROM incidents_calls ic + WHERE + ic.incident_id = $1 AND + ic.call_id = $2) +` + +func (q *Queries) CallInIncident(ctx context.Context, incidentID uuid.UUID, callID uuid.UUID) (bool, error) { + row := q.db.QueryRow(ctx, callInIncident, incidentID, callID) + var exists bool + err := row.Scan(&exists) + return exists, err +} + const createIncident = `-- name: CreateIncident :one INSERT INTO incidents ( id, diff --git a/pkg/database/models.go b/pkg/database/models.go index 585f860..257acee 100644 --- a/pkg/database/models.go +++ b/pkg/database/models.go @@ -14,150 +14,150 @@ import ( ) type Alert struct { - ID int `json:"id,omitempty"` - Time pgtype.Timestamptz `json:"time,omitempty"` - TGID int `json:"tgid,omitempty"` - SystemID int `json:"system_id,omitempty"` - Weight *float32 `json:"weight,omitempty"` - Score *float32 `json:"score,omitempty"` - OrigScore *float32 `json:"orig_score,omitempty"` - Notified bool `json:"notified,omitempty"` - Metadata []byte `json:"metadata,omitempty"` + ID int `json:"id"` + Time pgtype.Timestamptz `json:"time"` + TGID int `json:"tgid"` + SystemID int `json:"system_id"` + Weight *float32 `json:"weight"` + Score *float32 `json:"score"` + OrigScore *float32 `json:"orig_score"` + Notified bool `json:"notified"` + Metadata []byte `json:"metadata"` } type ApiKey struct { - ID int `json:"id,omitempty"` - Owner int `json:"owner,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - Expires pgtype.Timestamp `json:"expires,omitempty"` - Disabled *bool `json:"disabled,omitempty"` - ApiKey string `json:"api_key,omitempty"` + ID int `json:"id"` + Owner int `json:"owner"` + CreatedAt time.Time `json:"created_at"` + Expires pgtype.Timestamp `json:"expires"` + Disabled *bool `json:"disabled"` + ApiKey string `json:"api_key"` } type Call struct { - ID uuid.UUID `json:"id,omitempty"` - Submitter *int32 `json:"submitter,omitempty"` - System int `json:"system,omitempty"` - Talkgroup int `json:"talkgroup,omitempty"` - CallDate pgtype.Timestamptz `json:"call_date,omitempty"` - AudioName *string `json:"audio_name,omitempty"` - AudioBlob []byte `json:"audio_blob,omitempty"` - Duration *int32 `json:"duration,omitempty"` - AudioType *string `json:"audio_type,omitempty"` - AudioUrl *string `json:"audio_url,omitempty"` - Frequency int `json:"frequency,omitempty"` - Frequencies []int `json:"frequencies,omitempty"` - Patches []int `json:"patches,omitempty"` - TGLabel *string `json:"tg_label,omitempty"` - TGAlphaTag *string `json:"tg_alpha_tag,omitempty"` - TGGroup *string `json:"tg_group,omitempty"` - Source int `json:"source,omitempty"` - Transcript *string `json:"transcript,omitempty"` + ID uuid.UUID `json:"id"` + Submitter *int32 `json:"submitter"` + System int `json:"system"` + Talkgroup int `json:"talkgroup"` + CallDate pgtype.Timestamptz `json:"call_date"` + AudioName *string `json:"audio_name"` + AudioBlob []byte `json:"audio_blob"` + Duration *int32 `json:"duration"` + AudioType *string `json:"audio_type"` + AudioUrl *string `json:"audio_url"` + Frequency int `json:"frequency"` + Frequencies []int `json:"frequencies"` + Patches []int `json:"patches"` + TGLabel *string `json:"tg_label"` + TGAlphaTag *string `json:"tg_alpha_tag"` + TGGroup *string `json:"tg_group"` + Source int `json:"source"` + Transcript *string `json:"transcript"` } type Incident struct { - ID uuid.UUID `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Owner int `json:"owner,omitempty"` - Description *string `json:"description,omitempty"` - StartTime pgtype.Timestamptz `json:"start_time,omitempty"` - EndTime pgtype.Timestamptz `json:"end_time,omitempty"` - Location []byte `json:"location,omitempty"` - Metadata jsontypes.Metadata `json:"metadata,omitempty"` + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Owner int `json:"owner"` + Description *string `json:"description"` + StartTime pgtype.Timestamptz `json:"start_time"` + EndTime pgtype.Timestamptz `json:"end_time"` + Location []byte `json:"location"` + Metadata jsontypes.Metadata `json:"metadata"` } type IncidentsCall struct { - IncidentID uuid.UUID `json:"incident_id,omitempty"` - CallID uuid.UUID `json:"call_id,omitempty"` - CallsTblID pgtype.UUID `json:"calls_tbl_id,omitempty"` - SweptCallID pgtype.UUID `json:"swept_call_id,omitempty"` - CallDate pgtype.Timestamptz `json:"call_date,omitempty"` - Notes []byte `json:"notes,omitempty"` + IncidentID uuid.UUID `json:"incident_id"` + CallID uuid.UUID `json:"call_id"` + CallsTblID pgtype.UUID `json:"calls_tbl_id"` + SweptCallID pgtype.UUID `json:"swept_call_id"` + CallDate pgtype.Timestamptz `json:"call_date"` + Notes []byte `json:"notes"` } type Setting struct { - Name string `json:"name,omitempty"` - UpdatedBy *int32 `json:"updated_by,omitempty"` - Value []byte `json:"value,omitempty"` + Name string `json:"name"` + UpdatedBy *int32 `json:"updated_by"` + Value []byte `json:"value"` } type Share struct { - ID string `json:"id,omitempty"` - EntityType string `json:"entity_type,omitempty"` - EntityID uuid.UUID `json:"entity_id,omitempty"` - EntityDate pgtype.Timestamptz `json:"entity_date,omitempty"` - Owner int `json:"owner,omitempty"` - Expiration pgtype.Timestamptz `json:"expiration,omitempty"` + ID string `json:"id"` + EntityType string `json:"entity_type"` + EntityID uuid.UUID `json:"entity_id"` + EntityDate pgtype.Timestamptz `json:"entity_date"` + Owner int `json:"owner"` + Expiration pgtype.Timestamptz `json:"expiration"` } type SweptCall struct { - ID uuid.UUID `json:"id,omitempty"` - Submitter *int32 `json:"submitter,omitempty"` - System int `json:"system,omitempty"` - Talkgroup int `json:"talkgroup,omitempty"` - CallDate pgtype.Timestamptz `json:"call_date,omitempty"` - AudioName *string `json:"audio_name,omitempty"` - AudioBlob []byte `json:"audio_blob,omitempty"` - Duration *int32 `json:"duration,omitempty"` - AudioType *string `json:"audio_type,omitempty"` - AudioUrl *string `json:"audio_url,omitempty"` - Frequency int `json:"frequency,omitempty"` - Frequencies []int `json:"frequencies,omitempty"` - Patches []int `json:"patches,omitempty"` - TGLabel *string `json:"tg_label,omitempty"` - TGAlphaTag *string `json:"tg_alpha_tag,omitempty"` - TGGroup *string `json:"tg_group,omitempty"` - Source int `json:"source,omitempty"` - Transcript *string `json:"transcript,omitempty"` + ID uuid.UUID `json:"id"` + Submitter *int32 `json:"submitter"` + System int `json:"system"` + Talkgroup int `json:"talkgroup"` + CallDate pgtype.Timestamptz `json:"call_date"` + AudioName *string `json:"audio_name"` + AudioBlob []byte `json:"audio_blob"` + Duration *int32 `json:"duration"` + AudioType *string `json:"audio_type"` + AudioUrl *string `json:"audio_url"` + Frequency int `json:"frequency"` + Frequencies []int `json:"frequencies"` + Patches []int `json:"patches"` + TGLabel *string `json:"tg_label"` + TGAlphaTag *string `json:"tg_alpha_tag"` + TGGroup *string `json:"tg_group"` + Source int `json:"source"` + Transcript *string `json:"transcript"` } type System struct { - ID int `json:"id,omitempty"` - Name string `json:"name,omitempty"` + ID int `json:"id"` + Name string `json:"name"` } type Talkgroup struct { - ID int `json:"id,omitempty"` - SystemID int32 `json:"system_id,omitempty"` - TGID int32 `json:"tgid,omitempty"` - Name *string `json:"name,omitempty"` - AlphaTag *string `json:"alpha_tag,omitempty"` - TGGroup *string `json:"tg_group,omitempty"` - Frequency *int32 `json:"frequency,omitempty"` - Metadata jsontypes.Metadata `json:"metadata,omitempty"` - Tags []string `json:"tags,omitempty"` - Alert bool `json:"alert,omitempty"` - AlertRules rules.AlertRules `json:"alert_rules,omitempty"` - Weight float32 `json:"weight,omitempty"` - Learned bool `json:"learned,omitempty"` - Ignored bool `json:"ignored,omitempty"` + ID int `json:"id"` + SystemID int32 `json:"system_id"` + TGID int32 `json:"tgid"` + Name *string `json:"name"` + AlphaTag *string `json:"alpha_tag"` + TGGroup *string `json:"tg_group"` + Frequency *int32 `json:"frequency"` + Metadata jsontypes.Metadata `json:"metadata"` + Tags []string `json:"tags"` + Alert bool `json:"alert"` + AlertRules rules.AlertRules `json:"alert_rules"` + Weight float32 `json:"weight"` + Learned bool `json:"learned"` + Ignored bool `json:"ignored"` } type TalkgroupVersion struct { - ID int `json:"id,omitempty"` - Time pgtype.Timestamptz `json:"time,omitempty"` - CreatedBy *int32 `json:"created_by,omitempty"` - Deleted *bool `json:"deleted,omitempty"` - SystemID *int32 `json:"system_id,omitempty"` - TGID *int32 `json:"tgid,omitempty"` - Name *string `json:"name,omitempty"` - AlphaTag *string `json:"alpha_tag,omitempty"` - TGGroup *string `json:"tg_group,omitempty"` - Frequency *int32 `json:"frequency,omitempty"` - Metadata []byte `json:"metadata,omitempty"` - Tags []string `json:"tags,omitempty"` - Alert *bool `json:"alert,omitempty"` - AlertRules []byte `json:"alert_rules,omitempty"` - Weight *float32 `json:"weight,omitempty"` - Learned *bool `json:"learned,omitempty"` - Ignored *bool `json:"ignored,omitempty"` + ID int `json:"id"` + Time pgtype.Timestamptz `json:"time"` + CreatedBy *int32 `json:"created_by"` + Deleted *bool `json:"deleted"` + SystemID *int32 `json:"system_id"` + TGID *int32 `json:"tgid"` + Name *string `json:"name"` + AlphaTag *string `json:"alpha_tag"` + TGGroup *string `json:"tg_group"` + Frequency *int32 `json:"frequency"` + Metadata []byte `json:"metadata"` + Tags []string `json:"tags"` + Alert *bool `json:"alert"` + AlertRules []byte `json:"alert_rules"` + Weight *float32 `json:"weight"` + Learned *bool `json:"learned"` + Ignored *bool `json:"ignored"` } type User struct { - ID int `json:"id,omitempty"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Email string `json:"email,omitempty"` - IsAdmin bool `json:"is_admin,omitempty"` - Prefs []byte `json:"prefs,omitempty"` + ID int `json:"id"` + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` + IsAdmin bool `json:"is_admin"` + Prefs []byte `json:"prefs"` } diff --git a/pkg/database/querier.go b/pkg/database/querier.go index 4f8d8db..dfd32b1 100644 --- a/pkg/database/querier.go +++ b/pkg/database/querier.go @@ -16,6 +16,7 @@ type Querier interface { AddCall(ctx context.Context, arg AddCallParams) error AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (Talkgroup, error) AddToIncident(ctx context.Context, incidentID uuid.UUID, callIds []uuid.UUID, notes [][]byte) error + CallInIncident(ctx context.Context, incidentID uuid.UUID, callID uuid.UUID) (bool, error) CleanupSweptCalls(ctx context.Context, rangeStart pgtype.Timestamptz, rangeEnd pgtype.Timestamptz) (int64, error) CreateAPIKey(ctx context.Context, owner int, expires pgtype.Timestamp, disabled *bool) (ApiKey, error) CreateIncident(ctx context.Context, arg CreateIncidentParams) (Incident, error) diff --git a/pkg/incidents/incident.go b/pkg/incidents/incident.go index b48f152..37aa68c 100644 --- a/pkg/incidents/incident.go +++ b/pkg/incidents/incident.go @@ -5,7 +5,6 @@ import ( "dynatron.me/x/stillbox/internal/jsontypes" "dynatron.me/x/stillbox/pkg/calls" - "dynatron.me/x/stillbox/pkg/rbac" "dynatron.me/x/stillbox/pkg/users" "github.com/google/uuid" ) diff --git a/pkg/incidents/incstore/rbac.go b/pkg/incidents/incstore/rbac.go new file mode 100644 index 0000000..7f5af77 --- /dev/null +++ b/pkg/incidents/incstore/rbac.go @@ -0,0 +1,65 @@ +package incstore + +import ( + "context" + "errors" + "fmt" + + "github.com/el-mike/restrict/v2" + "github.com/google/uuid" +) + +const ( + CallInIncidentConditionType = "CALL_IN_INCIDENT" +) + +type CallInIncidentCondition struct { + ID string `json:"name,omitempty" yaml:"name,omitempty"` + Call *restrict.ValueDescriptor `json:"call" yaml:"call"` + Incident *restrict.ValueDescriptor `json:"incident" yaml:"incident"` +} + +func (*CallInIncidentCondition) Type() string { + return CallInIncidentConditionType +} + +func (c *CallInIncidentCondition) Check(r *restrict.AccessRequest) error { + callVID, err := c.Call.GetValue(r) + if err != nil { + return err + } + + incVID, err := c.Incident.GetValue(r) + if err != nil { + return err + } + + ctx, hasCtx := r.Context["ctx"].(context.Context) + if !hasCtx { + return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf("no context provided")) + } + + incID, isUUID := incVID.(uuid.UUID) + if !isUUID { + return restrict.NewConditionNotSatisfiedError(c, r, errors.New("incident ID is not UUID")) + } + + callID, isUUID := callVID.(uuid.UUID) + if !isUUID { + return restrict.NewConditionNotSatisfiedError(c, r, errors.New("call ID is not UUID")) + } + + incs := FromCtx(ctx) + inCall, err := incs.CallIn(ctx, incID, incID) + if err != nil { + return restrict.NewConditionNotSatisfiedError(c, r, err) + } + + if !inCall { + return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf(`incident "%v" not in call "%v"`, incID, callID)) + } + + return nil +} + + diff --git a/pkg/incidents/incstore/store.go b/pkg/incidents/incstore/store.go index e2f396f..44d64b1 100644 --- a/pkg/incidents/incstore/store.go +++ b/pkg/incidents/incstore/store.go @@ -49,6 +49,9 @@ type Store interface { // Owner returns an incident with only the owner filled out. Owner(ctx context.Context, id uuid.UUID) (incidents.Incident, error) + + // CallIn returns whether an incident is in an call + CallIn(ctx context.Context, inc uuid.UUID, call uuid.UUID) (bool, error) } type store struct { @@ -373,3 +376,9 @@ func (s *store) Owner(ctx context.Context, id uuid.UUID) (incidents.Incident, er owner, err := database.FromCtx(ctx).GetIncidentOwner(ctx, id) return incidents.Incident{ID: id, Owner: users.UserID(owner)}, err } + +func (s *store) CallIn(ctx context.Context, inc uuid.UUID, call uuid.UUID) (bool, error) { + db := database.FromCtx(ctx) + return db.CallInIncident(ctx, inc, call) +} + diff --git a/pkg/rbac/conditions.go b/pkg/rbac/conditions.go index 721e83d..4d89604 100644 --- a/pkg/rbac/conditions.go +++ b/pkg/rbac/conditions.go @@ -9,7 +9,7 @@ import ( const ( SubmitterEqualConditionType = "SUBMITTER_EQUAL" - InMapConditionType = "IN_MAP" + InMapConditionType = "IN_MAP" ) type SubmitterEqualCondition struct { @@ -53,7 +53,7 @@ func SubmitterEqualConditionFactory() restrict.Condition { } type InMapCondition[K comparable, V any] struct { - ID string `json:"name,omitempty" yaml:"name,omitempty"` + ID string `json:"name,omitempty" yaml:"name,omitempty"` Key *restrict.ValueDescriptor `json:"key" yaml:"key"` Map *restrict.ValueDescriptor `json:"map" yaml:"map"` } @@ -77,12 +77,9 @@ func (c *InMapCondition[K, V]) Check(r *restrict.AccessRequest) error { return err } - keyVal := reflect.ValueOf(cKey) - mapVal := reflect.ValueOf(cMap) + key := cKey.(K) - key := keyVal.Interface().(K) - - if _, in := mapVal.Interface().(map[K]V)[key]; !in { + if _, in := cMap.(map[K]V)[key]; !in { return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf("key '%v' not in map", key)) } diff --git a/pkg/rbac/entities.go b/pkg/rbac/entities.go new file mode 100644 index 0000000..9c710eb --- /dev/null +++ b/pkg/rbac/entities.go @@ -0,0 +1,23 @@ +package rbac + +const ( + RoleUser = "User" + RoleSubmitter = "Submitter" + RoleAdmin = "Admin" + RoleSystem = "System" + RolePublic = "Public" + RoleShareGuest = "ShareGuest" + + ResourceCall = "Call" + ResourceIncident = "Incident" + ResourceTalkgroup = "Talkgroup" + ResourceAlert = "Alert" + ResourceShare = "Share" + ResourceAPIKey = "APIKey" + + ActionRead = "read" + ActionCreate = "create" + ActionUpdate = "update" + ActionDelete = "delete" + ActionShare = "share" +) diff --git a/pkg/rbac/policy.go b/pkg/rbac/policy.go index 32e20da..894903d 100644 --- a/pkg/rbac/policy.go +++ b/pkg/rbac/policy.go @@ -1,10 +1,25 @@ package rbac import ( + "dynatron.me/x/stillbox/pkg/incidents/incstore" + "github.com/el-mike/restrict/v2" ) -var policy = &restrict.PolicyDefinition{ +const ( + PresetUpdateOwn = "updateOwn" + PresetDeleteOwn = "deleteOwn" + PresetReadShared = "readShared" + PresetReadSharedInMap = "readSharedInMap" + PresetShareOwn = "shareOwn" + + PresetUpdateSubmitter = "updateSubmitter" + PresetDeleteSubmitter = "deleteSubmitter" + PresetShareSubmitter = "shareSubmitter" + PresetReadInSharedIncident = "readInSharedIncident" +) + +var Policy = &restrict.PolicyDefinition{ Roles: restrict.Roles{ RoleUser: { Description: "An authenticated user", @@ -52,6 +67,7 @@ var policy = &restrict.PolicyDefinition{ Grants: restrict.GrantsMap{ ResourceCall: { &restrict.Permission{Preset: PresetReadShared}, + &restrict.Permission{Preset: PresetReadInSharedIncident}, }, ResourceIncident: { &restrict.Permission{Preset: PresetReadShared}, @@ -207,5 +223,21 @@ var policy = &restrict.PolicyDefinition{ }, }, }, + PresetReadInSharedIncident: &restrict.Permission{ + Action: ActionRead, + Conditions: restrict.Conditions{ + &incstore.CallInIncidentCondition{ + ID: "callInIncident", + Call: &restrict.ValueDescriptor{ + Source: restrict.ResourceField, + Field: "ID", + }, + Incident: &restrict.ValueDescriptor{ + Source: restrict.SubjectField, + Field: "EntityID", + }, + }, + }, + }, }, } diff --git a/pkg/rbac/rbac.go b/pkg/rbac/rbac.go index 9a9525c..778a5c2 100644 --- a/pkg/rbac/rbac.go +++ b/pkg/rbac/rbac.go @@ -8,37 +8,7 @@ import ( "github.com/el-mike/restrict/v2/adapters" ) -const ( - RoleUser = "User" - RoleSubmitter = "Submitter" - RoleAdmin = "Admin" - RoleSystem = "System" - RolePublic = "Public" - RoleShareGuest = "ShareGuest" - ResourceCall = "Call" - ResourceIncident = "Incident" - ResourceTalkgroup = "Talkgroup" - ResourceAlert = "Alert" - ResourceShare = "Share" - ResourceAPIKey = "APIKey" - - ActionRead = "read" - ActionCreate = "create" - ActionUpdate = "update" - ActionDelete = "delete" - ActionShare = "share" - - PresetUpdateOwn = "updateOwn" - PresetDeleteOwn = "deleteOwn" - PresetReadShared = "readShared" - PresetReadSharedInMap = "readSharedInMap" - PresetShareOwn = "shareOwn" - - PresetUpdateSubmitter = "updateSubmitter" - PresetDeleteSubmitter = "deleteSubmitter" - PresetShareSubmitter = "shareSubmitter" -) var ( ErrBadSubject = errors.New("bad subject in token") @@ -132,7 +102,7 @@ type rbac struct { } func New() (*rbac, error) { - adapter := adapters.NewInMemoryAdapter(policy) + adapter := adapters.NewInMemoryAdapter(Policy) polMan, err := restrict.NewPolicyManager(adapter, true) if err != nil { return nil, err @@ -154,10 +124,17 @@ func (r *rbac) Check(ctx context.Context, res restrict.Resource, opts ...CheckOp sub := SubjectFrom(ctx) o := checkOptions{} + for _, opt := range opts { opt(&o) } + if o.context == nil { + o.context = make(restrict.Context) + } + + o.context["ctx"] = ctx + req := &restrict.AccessRequest{ Subject: sub, Resource: res, diff --git a/pkg/server/routes.go b/pkg/server/routes.go index 43fa02f..9410baf 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -2,7 +2,6 @@ package server import ( "errors" - "fmt" "io/fs" "net/http" "path" diff --git a/pkg/talkgroups/talkgroup.go b/pkg/talkgroups/talkgroup.go index 7965e98..c2b306f 100644 --- a/pkg/talkgroups/talkgroup.go +++ b/pkg/talkgroups/talkgroup.go @@ -9,7 +9,10 @@ import ( "strings" "dynatron.me/x/stillbox/pkg/database" - "dynatron.me/x/stillbox/pkg/rbac" +) + +const ( + Resource = "Talkgroup" ) type Talkgroup struct { @@ -19,7 +22,7 @@ type Talkgroup struct { } func (t *Talkgroup) GetResourceName() string { - return rbac.ResourceTalkgroup + return Resource } func (t Talkgroup) String() string { diff --git a/pkg/talkgroups/tgstore/store.go b/pkg/talkgroups/tgstore/store.go index 64a010e..42a347c 100644 --- a/pkg/talkgroups/tgstore/store.go +++ b/pkg/talkgroups/tgstore/store.go @@ -327,7 +327,7 @@ func addToRowList[T rowType](t *cache, tgRecords []T) []*tgsp.Talkgroup { } func (t *cache) TGs(ctx context.Context, tgs tgsp.IDs, opts ...Option) ([]*tgsp.Talkgroup, error) { - _, err := rbac.Check(ctx, rbac.UseResource(rbac.ResourceTalkgroup), rbac.WithActions(rbac.ActionRead)) + _, err := rbac.Check(ctx, rbac.UseResource(tgsp.Resource), rbac.WithActions(rbac.ActionRead)) if err != nil { return nil, err } diff --git a/pkg/users/guest.go b/pkg/users/guest.go deleted file mode 100644 index 52657e7..0000000 --- a/pkg/users/guest.go +++ /dev/null @@ -1,13 +0,0 @@ -package users - -import ( - "dynatron.me/x/stillbox/pkg/rbac" -) - -type Public struct { - RemoteAddr string -} - -func (s *Public) GetRoles() []string { - return []string{rbac.RolePublic} -} diff --git a/sql/postgres/queries/incidents.sql b/sql/postgres/queries/incidents.sql index 98b3fc8..0bc1e89 100644 --- a/sql/postgres/queries/incidents.sql +++ b/sql/postgres/queries/incidents.sql @@ -29,6 +29,13 @@ UPDATE incidents_Calls SET notes = @notes WHERE incident_id = @incident_id AND call_id = @call_id; +-- name: CallInIncident :one +SELECT EXISTS + (SELECT 1 FROM incidents_calls ic + WHERE + ic.incident_id = @incident_id AND + ic.call_id = @call_id); + -- name: CreateIncident :one INSERT INTO incidents ( id,