WIP cycle
This commit is contained in:
parent
b171e8431a
commit
5ff3066d6d
16 changed files with 287 additions and 172 deletions
|
@ -8,7 +8,6 @@ import (
|
||||||
"dynatron.me/x/stillbox/internal/audio"
|
"dynatron.me/x/stillbox/internal/audio"
|
||||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"dynatron.me/x/stillbox/pkg/pb"
|
"dynatron.me/x/stillbox/pkg/pb"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
|
|
||||||
|
@ -16,6 +15,8 @@ import (
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const Resource = "call"
|
||||||
|
|
||||||
type CallDuration time.Duration
|
type CallDuration time.Duration
|
||||||
|
|
||||||
func (d CallDuration) Duration() time.Duration {
|
func (d CallDuration) Duration() time.Duration {
|
||||||
|
@ -76,7 +77,7 @@ type Call struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Call) GetResourceName() string {
|
func (c *Call) GetResourceName() string {
|
||||||
return rbac.ResourceCall
|
return Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Call) String() string {
|
func (c *Call) String() string {
|
||||||
|
|
|
@ -40,6 +40,21 @@ func (q *Queries) AddToIncident(ctx context.Context, incidentID uuid.UUID, callI
|
||||||
return err
|
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
|
const createIncident = `-- name: CreateIncident :one
|
||||||
INSERT INTO incidents (
|
INSERT INTO incidents (
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -14,150 +14,150 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Alert struct {
|
type Alert struct {
|
||||||
ID int `json:"id,omitempty"`
|
ID int `json:"id"`
|
||||||
Time pgtype.Timestamptz `json:"time,omitempty"`
|
Time pgtype.Timestamptz `json:"time"`
|
||||||
TGID int `json:"tgid,omitempty"`
|
TGID int `json:"tgid"`
|
||||||
SystemID int `json:"system_id,omitempty"`
|
SystemID int `json:"system_id"`
|
||||||
Weight *float32 `json:"weight,omitempty"`
|
Weight *float32 `json:"weight"`
|
||||||
Score *float32 `json:"score,omitempty"`
|
Score *float32 `json:"score"`
|
||||||
OrigScore *float32 `json:"orig_score,omitempty"`
|
OrigScore *float32 `json:"orig_score"`
|
||||||
Notified bool `json:"notified,omitempty"`
|
Notified bool `json:"notified"`
|
||||||
Metadata []byte `json:"metadata,omitempty"`
|
Metadata []byte `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiKey struct {
|
type ApiKey struct {
|
||||||
ID int `json:"id,omitempty"`
|
ID int `json:"id"`
|
||||||
Owner int `json:"owner,omitempty"`
|
Owner int `json:"owner"`
|
||||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
Expires pgtype.Timestamp `json:"expires,omitempty"`
|
Expires pgtype.Timestamp `json:"expires"`
|
||||||
Disabled *bool `json:"disabled,omitempty"`
|
Disabled *bool `json:"disabled"`
|
||||||
ApiKey string `json:"api_key,omitempty"`
|
ApiKey string `json:"api_key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Call struct {
|
type Call struct {
|
||||||
ID uuid.UUID `json:"id,omitempty"`
|
ID uuid.UUID `json:"id"`
|
||||||
Submitter *int32 `json:"submitter,omitempty"`
|
Submitter *int32 `json:"submitter"`
|
||||||
System int `json:"system,omitempty"`
|
System int `json:"system"`
|
||||||
Talkgroup int `json:"talkgroup,omitempty"`
|
Talkgroup int `json:"talkgroup"`
|
||||||
CallDate pgtype.Timestamptz `json:"call_date,omitempty"`
|
CallDate pgtype.Timestamptz `json:"call_date"`
|
||||||
AudioName *string `json:"audio_name,omitempty"`
|
AudioName *string `json:"audio_name"`
|
||||||
AudioBlob []byte `json:"audio_blob,omitempty"`
|
AudioBlob []byte `json:"audio_blob"`
|
||||||
Duration *int32 `json:"duration,omitempty"`
|
Duration *int32 `json:"duration"`
|
||||||
AudioType *string `json:"audio_type,omitempty"`
|
AudioType *string `json:"audio_type"`
|
||||||
AudioUrl *string `json:"audio_url,omitempty"`
|
AudioUrl *string `json:"audio_url"`
|
||||||
Frequency int `json:"frequency,omitempty"`
|
Frequency int `json:"frequency"`
|
||||||
Frequencies []int `json:"frequencies,omitempty"`
|
Frequencies []int `json:"frequencies"`
|
||||||
Patches []int `json:"patches,omitempty"`
|
Patches []int `json:"patches"`
|
||||||
TGLabel *string `json:"tg_label,omitempty"`
|
TGLabel *string `json:"tg_label"`
|
||||||
TGAlphaTag *string `json:"tg_alpha_tag,omitempty"`
|
TGAlphaTag *string `json:"tg_alpha_tag"`
|
||||||
TGGroup *string `json:"tg_group,omitempty"`
|
TGGroup *string `json:"tg_group"`
|
||||||
Source int `json:"source,omitempty"`
|
Source int `json:"source"`
|
||||||
Transcript *string `json:"transcript,omitempty"`
|
Transcript *string `json:"transcript"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Incident struct {
|
type Incident struct {
|
||||||
ID uuid.UUID `json:"id,omitempty"`
|
ID uuid.UUID `json:"id"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name"`
|
||||||
Owner int `json:"owner,omitempty"`
|
Owner int `json:"owner"`
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description"`
|
||||||
StartTime pgtype.Timestamptz `json:"start_time,omitempty"`
|
StartTime pgtype.Timestamptz `json:"start_time"`
|
||||||
EndTime pgtype.Timestamptz `json:"end_time,omitempty"`
|
EndTime pgtype.Timestamptz `json:"end_time"`
|
||||||
Location []byte `json:"location,omitempty"`
|
Location []byte `json:"location"`
|
||||||
Metadata jsontypes.Metadata `json:"metadata,omitempty"`
|
Metadata jsontypes.Metadata `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IncidentsCall struct {
|
type IncidentsCall struct {
|
||||||
IncidentID uuid.UUID `json:"incident_id,omitempty"`
|
IncidentID uuid.UUID `json:"incident_id"`
|
||||||
CallID uuid.UUID `json:"call_id,omitempty"`
|
CallID uuid.UUID `json:"call_id"`
|
||||||
CallsTblID pgtype.UUID `json:"calls_tbl_id,omitempty"`
|
CallsTblID pgtype.UUID `json:"calls_tbl_id"`
|
||||||
SweptCallID pgtype.UUID `json:"swept_call_id,omitempty"`
|
SweptCallID pgtype.UUID `json:"swept_call_id"`
|
||||||
CallDate pgtype.Timestamptz `json:"call_date,omitempty"`
|
CallDate pgtype.Timestamptz `json:"call_date"`
|
||||||
Notes []byte `json:"notes,omitempty"`
|
Notes []byte `json:"notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Setting struct {
|
type Setting struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name"`
|
||||||
UpdatedBy *int32 `json:"updated_by,omitempty"`
|
UpdatedBy *int32 `json:"updated_by"`
|
||||||
Value []byte `json:"value,omitempty"`
|
Value []byte `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Share struct {
|
type Share struct {
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id"`
|
||||||
EntityType string `json:"entity_type,omitempty"`
|
EntityType string `json:"entity_type"`
|
||||||
EntityID uuid.UUID `json:"entity_id,omitempty"`
|
EntityID uuid.UUID `json:"entity_id"`
|
||||||
EntityDate pgtype.Timestamptz `json:"entity_date,omitempty"`
|
EntityDate pgtype.Timestamptz `json:"entity_date"`
|
||||||
Owner int `json:"owner,omitempty"`
|
Owner int `json:"owner"`
|
||||||
Expiration pgtype.Timestamptz `json:"expiration,omitempty"`
|
Expiration pgtype.Timestamptz `json:"expiration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SweptCall struct {
|
type SweptCall struct {
|
||||||
ID uuid.UUID `json:"id,omitempty"`
|
ID uuid.UUID `json:"id"`
|
||||||
Submitter *int32 `json:"submitter,omitempty"`
|
Submitter *int32 `json:"submitter"`
|
||||||
System int `json:"system,omitempty"`
|
System int `json:"system"`
|
||||||
Talkgroup int `json:"talkgroup,omitempty"`
|
Talkgroup int `json:"talkgroup"`
|
||||||
CallDate pgtype.Timestamptz `json:"call_date,omitempty"`
|
CallDate pgtype.Timestamptz `json:"call_date"`
|
||||||
AudioName *string `json:"audio_name,omitempty"`
|
AudioName *string `json:"audio_name"`
|
||||||
AudioBlob []byte `json:"audio_blob,omitempty"`
|
AudioBlob []byte `json:"audio_blob"`
|
||||||
Duration *int32 `json:"duration,omitempty"`
|
Duration *int32 `json:"duration"`
|
||||||
AudioType *string `json:"audio_type,omitempty"`
|
AudioType *string `json:"audio_type"`
|
||||||
AudioUrl *string `json:"audio_url,omitempty"`
|
AudioUrl *string `json:"audio_url"`
|
||||||
Frequency int `json:"frequency,omitempty"`
|
Frequency int `json:"frequency"`
|
||||||
Frequencies []int `json:"frequencies,omitempty"`
|
Frequencies []int `json:"frequencies"`
|
||||||
Patches []int `json:"patches,omitempty"`
|
Patches []int `json:"patches"`
|
||||||
TGLabel *string `json:"tg_label,omitempty"`
|
TGLabel *string `json:"tg_label"`
|
||||||
TGAlphaTag *string `json:"tg_alpha_tag,omitempty"`
|
TGAlphaTag *string `json:"tg_alpha_tag"`
|
||||||
TGGroup *string `json:"tg_group,omitempty"`
|
TGGroup *string `json:"tg_group"`
|
||||||
Source int `json:"source,omitempty"`
|
Source int `json:"source"`
|
||||||
Transcript *string `json:"transcript,omitempty"`
|
Transcript *string `json:"transcript"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type System struct {
|
type System struct {
|
||||||
ID int `json:"id,omitempty"`
|
ID int `json:"id"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Talkgroup struct {
|
type Talkgroup struct {
|
||||||
ID int `json:"id,omitempty"`
|
ID int `json:"id"`
|
||||||
SystemID int32 `json:"system_id,omitempty"`
|
SystemID int32 `json:"system_id"`
|
||||||
TGID int32 `json:"tgid,omitempty"`
|
TGID int32 `json:"tgid"`
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name"`
|
||||||
AlphaTag *string `json:"alpha_tag,omitempty"`
|
AlphaTag *string `json:"alpha_tag"`
|
||||||
TGGroup *string `json:"tg_group,omitempty"`
|
TGGroup *string `json:"tg_group"`
|
||||||
Frequency *int32 `json:"frequency,omitempty"`
|
Frequency *int32 `json:"frequency"`
|
||||||
Metadata jsontypes.Metadata `json:"metadata,omitempty"`
|
Metadata jsontypes.Metadata `json:"metadata"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags"`
|
||||||
Alert bool `json:"alert,omitempty"`
|
Alert bool `json:"alert"`
|
||||||
AlertRules rules.AlertRules `json:"alert_rules,omitempty"`
|
AlertRules rules.AlertRules `json:"alert_rules"`
|
||||||
Weight float32 `json:"weight,omitempty"`
|
Weight float32 `json:"weight"`
|
||||||
Learned bool `json:"learned,omitempty"`
|
Learned bool `json:"learned"`
|
||||||
Ignored bool `json:"ignored,omitempty"`
|
Ignored bool `json:"ignored"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TalkgroupVersion struct {
|
type TalkgroupVersion struct {
|
||||||
ID int `json:"id,omitempty"`
|
ID int `json:"id"`
|
||||||
Time pgtype.Timestamptz `json:"time,omitempty"`
|
Time pgtype.Timestamptz `json:"time"`
|
||||||
CreatedBy *int32 `json:"created_by,omitempty"`
|
CreatedBy *int32 `json:"created_by"`
|
||||||
Deleted *bool `json:"deleted,omitempty"`
|
Deleted *bool `json:"deleted"`
|
||||||
SystemID *int32 `json:"system_id,omitempty"`
|
SystemID *int32 `json:"system_id"`
|
||||||
TGID *int32 `json:"tgid,omitempty"`
|
TGID *int32 `json:"tgid"`
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name"`
|
||||||
AlphaTag *string `json:"alpha_tag,omitempty"`
|
AlphaTag *string `json:"alpha_tag"`
|
||||||
TGGroup *string `json:"tg_group,omitempty"`
|
TGGroup *string `json:"tg_group"`
|
||||||
Frequency *int32 `json:"frequency,omitempty"`
|
Frequency *int32 `json:"frequency"`
|
||||||
Metadata []byte `json:"metadata,omitempty"`
|
Metadata []byte `json:"metadata"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags"`
|
||||||
Alert *bool `json:"alert,omitempty"`
|
Alert *bool `json:"alert"`
|
||||||
AlertRules []byte `json:"alert_rules,omitempty"`
|
AlertRules []byte `json:"alert_rules"`
|
||||||
Weight *float32 `json:"weight,omitempty"`
|
Weight *float32 `json:"weight"`
|
||||||
Learned *bool `json:"learned,omitempty"`
|
Learned *bool `json:"learned"`
|
||||||
Ignored *bool `json:"ignored,omitempty"`
|
Ignored *bool `json:"ignored"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int `json:"id,omitempty"`
|
ID int `json:"id"`
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password"`
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email"`
|
||||||
IsAdmin bool `json:"is_admin,omitempty"`
|
IsAdmin bool `json:"is_admin"`
|
||||||
Prefs []byte `json:"prefs,omitempty"`
|
Prefs []byte `json:"prefs"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ type Querier interface {
|
||||||
AddCall(ctx context.Context, arg AddCallParams) error
|
AddCall(ctx context.Context, arg AddCallParams) error
|
||||||
AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (Talkgroup, error)
|
AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (Talkgroup, error)
|
||||||
AddToIncident(ctx context.Context, incidentID uuid.UUID, callIds []uuid.UUID, notes [][]byte) 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)
|
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)
|
CreateAPIKey(ctx context.Context, owner int, expires pgtype.Timestamp, disabled *bool) (ApiKey, error)
|
||||||
CreateIncident(ctx context.Context, arg CreateIncidentParams) (Incident, error)
|
CreateIncident(ctx context.Context, arg CreateIncidentParams) (Incident, error)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
65
pkg/incidents/incstore/rbac.go
Normal file
65
pkg/incidents/incstore/rbac.go
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,9 @@ type Store interface {
|
||||||
|
|
||||||
// Owner returns an incident with only the owner filled out.
|
// Owner returns an incident with only the owner filled out.
|
||||||
Owner(ctx context.Context, id uuid.UUID) (incidents.Incident, error)
|
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 {
|
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)
|
owner, err := database.FromCtx(ctx).GetIncidentOwner(ctx, id)
|
||||||
return incidents.Incident{ID: id, Owner: users.UserID(owner)}, err
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,12 +77,9 @@ func (c *InMapCondition[K, V]) Check(r *restrict.AccessRequest) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyVal := reflect.ValueOf(cKey)
|
key := cKey.(K)
|
||||||
mapVal := reflect.ValueOf(cMap)
|
|
||||||
|
|
||||||
key := keyVal.Interface().(K)
|
if _, in := cMap.(map[K]V)[key]; !in {
|
||||||
|
|
||||||
if _, in := mapVal.Interface().(map[K]V)[key]; !in {
|
|
||||||
return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf("key '%v' not in map", key))
|
return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf("key '%v' not in map", key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
pkg/rbac/entities.go
Normal file
23
pkg/rbac/entities.go
Normal file
|
@ -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"
|
||||||
|
)
|
|
@ -1,10 +1,25 @@
|
||||||
package rbac
|
package rbac
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||||||
|
|
||||||
"github.com/el-mike/restrict/v2"
|
"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{
|
Roles: restrict.Roles{
|
||||||
RoleUser: {
|
RoleUser: {
|
||||||
Description: "An authenticated user",
|
Description: "An authenticated user",
|
||||||
|
@ -52,6 +67,7 @@ var policy = &restrict.PolicyDefinition{
|
||||||
Grants: restrict.GrantsMap{
|
Grants: restrict.GrantsMap{
|
||||||
ResourceCall: {
|
ResourceCall: {
|
||||||
&restrict.Permission{Preset: PresetReadShared},
|
&restrict.Permission{Preset: PresetReadShared},
|
||||||
|
&restrict.Permission{Preset: PresetReadInSharedIncident},
|
||||||
},
|
},
|
||||||
ResourceIncident: {
|
ResourceIncident: {
|
||||||
&restrict.Permission{Preset: PresetReadShared},
|
&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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,37 +8,7 @@ import (
|
||||||
"github.com/el-mike/restrict/v2/adapters"
|
"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 (
|
var (
|
||||||
ErrBadSubject = errors.New("bad subject in token")
|
ErrBadSubject = errors.New("bad subject in token")
|
||||||
|
@ -132,7 +102,7 @@ type rbac struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() (*rbac, error) {
|
func New() (*rbac, error) {
|
||||||
adapter := adapters.NewInMemoryAdapter(policy)
|
adapter := adapters.NewInMemoryAdapter(Policy)
|
||||||
polMan, err := restrict.NewPolicyManager(adapter, true)
|
polMan, err := restrict.NewPolicyManager(adapter, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -154,10 +124,17 @@ func (r *rbac) Check(ctx context.Context, res restrict.Resource, opts ...CheckOp
|
||||||
sub := SubjectFrom(ctx)
|
sub := SubjectFrom(ctx)
|
||||||
o := checkOptions{}
|
o := checkOptions{}
|
||||||
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&o)
|
opt(&o)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.context == nil {
|
||||||
|
o.context = make(restrict.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
o.context["ctx"] = ctx
|
||||||
|
|
||||||
req := &restrict.AccessRequest{
|
req := &restrict.AccessRequest{
|
||||||
Subject: sub,
|
Subject: sub,
|
||||||
Resource: res,
|
Resource: res,
|
||||||
|
|
|
@ -2,7 +2,6 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
|
|
@ -9,7 +9,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Resource = "Talkgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Talkgroup struct {
|
type Talkgroup struct {
|
||||||
|
@ -19,7 +22,7 @@ type Talkgroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Talkgroup) GetResourceName() string {
|
func (t *Talkgroup) GetResourceName() string {
|
||||||
return rbac.ResourceTalkgroup
|
return Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Talkgroup) String() string {
|
func (t Talkgroup) String() string {
|
||||||
|
|
|
@ -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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
|
||||||
}
|
|
|
@ -29,6 +29,13 @@ UPDATE incidents_Calls
|
||||||
SET notes = @notes
|
SET notes = @notes
|
||||||
WHERE incident_id = @incident_id AND call_id = @call_id;
|
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
|
-- name: CreateIncident :one
|
||||||
INSERT INTO incidents (
|
INSERT INTO incidents (
|
||||||
id,
|
id,
|
||||||
|
|
Loading…
Reference in a new issue