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/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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
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(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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
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
|
||||
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -2,7 +2,6 @@ package server
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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,
|
||||
|
|
Loading…
Reference in a new issue