Able to use store in rbac policy finally
This commit is contained in:
parent
957aebe695
commit
76a2214377
31 changed files with 510 additions and 332 deletions
|
@ -14,7 +14,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/notify"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/sinks"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
|
@ -124,7 +124,7 @@ func New(cfg config.Alerting, tgCache tgstore.Store, opts ...AlertOption) Alerte
|
|||
|
||||
// Go is the alerting loop. It does not start a goroutine.
|
||||
func (as *alerter) Go(ctx context.Context) {
|
||||
ctx = rbac.CtxWithSubject(ctx, &rbac.SystemServiceSubject{Name: "alerter"})
|
||||
ctx = entities.CtxWithSubject(ctx, &entities.SystemServiceSubject{Name: "alerter"})
|
||||
|
||||
err := as.startBackfill(ctx)
|
||||
if err != nil {
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
@ -16,10 +16,10 @@ import (
|
|||
type apiKeyAuth interface {
|
||||
// CheckAPIKey validates the provided key and returns the API owner's users.UserID.
|
||||
// An error is returned if validation fails for any reason.
|
||||
CheckAPIKey(ctx context.Context, key string) (rbac.Subject, error)
|
||||
CheckAPIKey(ctx context.Context, key string) (entities.Subject, error)
|
||||
}
|
||||
|
||||
func (a *Auth) CheckAPIKey(ctx context.Context, key string) (rbac.Subject, error) {
|
||||
func (a *Auth) CheckAPIKey(ctx context.Context, key string) (entities.Subject, error) {
|
||||
keyUuid, err := uuid.Parse(key)
|
||||
if err != nil {
|
||||
log.Error().Str("apikey", key).Msg("cannot parse key")
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
@ -104,7 +104,7 @@ func (a *Auth) AuthMiddleware() func(http.Handler) http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
ctx = rbac.CtxWithSubject(ctx, sub)
|
||||
ctx = entities.CtxWithSubject(ctx, sub)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ 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/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
|
||||
|
@ -76,7 +76,7 @@ type Call struct {
|
|||
}
|
||||
|
||||
func (c *Call) GetResourceName() string {
|
||||
return rbac.ResourceCall
|
||||
return entities.ResourceCall
|
||||
}
|
||||
|
||||
func (c *Call) String() string {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
|
||||
|
@ -85,7 +86,7 @@ func toAddCallParams(call *calls.Call) database.AddCallParams {
|
|||
}
|
||||
|
||||
func (s *store) AddCall(ctx context.Context, call *calls.Call) error {
|
||||
_, err := rbac.Check(ctx, call, rbac.WithActions(rbac.ActionCreate))
|
||||
_, err := rbac.Check(ctx, call, rbac.WithActions(entities.ActionCreate))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -123,7 +124,7 @@ func (s *store) AddCall(ctx context.Context, call *calls.Call) error {
|
|||
}
|
||||
|
||||
func (s *store) CallAudio(ctx context.Context, id uuid.UUID) (*calls.CallAudio, error) {
|
||||
_, err := rbac.Check(ctx, &calls.Call{ID: id}, rbac.WithActions(rbac.ActionRead))
|
||||
_, err := rbac.Check(ctx, &calls.Call{ID: id}, rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -144,7 +145,7 @@ func (s *store) CallAudio(ctx context.Context, id uuid.UUID) (*calls.CallAudio,
|
|||
}
|
||||
|
||||
func (s *store) Call(ctx context.Context, id uuid.UUID) (*calls.Call, error) {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(rbac.ResourceCall), rbac.WithActions(rbac.ActionRead))
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceCall), rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -193,7 +194,7 @@ type CallsParams struct {
|
|||
}
|
||||
|
||||
func (s *store) Calls(ctx context.Context, p CallsParams) (rows []database.ListCallsPRow, totalCount int, err error) {
|
||||
_, err = rbac.Check(ctx, rbac.UseResource(rbac.ResourceCall), rbac.WithActions(rbac.ActionRead))
|
||||
_, err = rbac.Check(ctx, rbac.UseResource(entities.ResourceCall), rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
@ -256,7 +257,7 @@ func (s *store) Delete(ctx context.Context, id uuid.UUID) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = rbac.Check(ctx, &callOwn, rbac.WithActions(rbac.ActionDelete))
|
||||
_, err = rbac.Check(ctx, &callOwn, rbac.WithActions(entities.ActionDelete))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -278,6 +278,64 @@ func (_c *Store_BulkSetTalkgroupTags_Call) RunAndReturn(run func(context.Context
|
|||
return _c
|
||||
}
|
||||
|
||||
// CallInIncident provides a mock function with given fields: ctx, incidentID, callID
|
||||
func (_m *Store) CallInIncident(ctx context.Context, incidentID uuid.UUID, callID uuid.UUID) (bool, error) {
|
||||
ret := _m.Called(ctx, incidentID, callID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CallInIncident")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID, uuid.UUID) (bool, error)); ok {
|
||||
return rf(ctx, incidentID, callID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID, uuid.UUID) bool); ok {
|
||||
r0 = rf(ctx, incidentID, callID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID, uuid.UUID) error); ok {
|
||||
r1 = rf(ctx, incidentID, callID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_CallInIncident_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CallInIncident'
|
||||
type Store_CallInIncident_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CallInIncident is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - incidentID uuid.UUID
|
||||
// - callID uuid.UUID
|
||||
func (_e *Store_Expecter) CallInIncident(ctx interface{}, incidentID interface{}, callID interface{}) *Store_CallInIncident_Call {
|
||||
return &Store_CallInIncident_Call{Call: _e.mock.On("CallInIncident", ctx, incidentID, callID)}
|
||||
}
|
||||
|
||||
func (_c *Store_CallInIncident_Call) Run(run func(ctx context.Context, incidentID uuid.UUID, callID uuid.UUID)) *Store_CallInIncident_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(uuid.UUID), args[2].(uuid.UUID))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_CallInIncident_Call) Return(_a0 bool, _a1 error) *Store_CallInIncident_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_CallInIncident_Call) RunAndReturn(run func(context.Context, uuid.UUID, uuid.UUID) (bool, error)) *Store_CallInIncident_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CleanupSweptCalls provides a mock function with given fields: ctx, rangeStart, rangeEnd
|
||||
func (_m *Store) CleanupSweptCalls(ctx context.Context, rangeStart pgtype.Timestamptz, rangeEnd pgtype.Timestamptz) (int64, error) {
|
||||
ret := _m.Called(ctx, rangeStart, rangeEnd)
|
||||
|
@ -1750,6 +1808,65 @@ func (_c *Store_GetIncidentOwner_Call) RunAndReturn(run func(context.Context, uu
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetIncidentTalkgroups provides a mock function with given fields: ctx, incidentID
|
||||
func (_m *Store) GetIncidentTalkgroups(ctx context.Context, incidentID uuid.UUID) ([]database.GetIncidentTalkgroupsRow, error) {
|
||||
ret := _m.Called(ctx, incidentID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetIncidentTalkgroups")
|
||||
}
|
||||
|
||||
var r0 []database.GetIncidentTalkgroupsRow
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) ([]database.GetIncidentTalkgroupsRow, error)); ok {
|
||||
return rf(ctx, incidentID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) []database.GetIncidentTalkgroupsRow); ok {
|
||||
r0 = rf(ctx, incidentID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]database.GetIncidentTalkgroupsRow)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok {
|
||||
r1 = rf(ctx, incidentID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_GetIncidentTalkgroups_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetIncidentTalkgroups'
|
||||
type Store_GetIncidentTalkgroups_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetIncidentTalkgroups is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - incidentID uuid.UUID
|
||||
func (_e *Store_Expecter) GetIncidentTalkgroups(ctx interface{}, incidentID interface{}) *Store_GetIncidentTalkgroups_Call {
|
||||
return &Store_GetIncidentTalkgroups_Call{Call: _e.mock.On("GetIncidentTalkgroups", ctx, incidentID)}
|
||||
}
|
||||
|
||||
func (_c *Store_GetIncidentTalkgroups_Call) Run(run func(ctx context.Context, incidentID uuid.UUID)) *Store_GetIncidentTalkgroups_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(uuid.UUID))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_GetIncidentTalkgroups_Call) Return(_a0 []database.GetIncidentTalkgroupsRow, _a1 error) *Store_GetIncidentTalkgroups_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_GetIncidentTalkgroups_Call) RunAndReturn(run func(context.Context, uuid.UUID) ([]database.GetIncidentTalkgroupsRow, error)) *Store_GetIncidentTalkgroups_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetShare provides a mock function with given fields: ctx, id
|
||||
func (_m *Store) GetShare(ctx context.Context, id string) (database.Share, error) {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
|
|
@ -14,150 +14,150 @@ import (
|
|||
)
|
||||
|
||||
type Alert struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type ApiKey struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type Call struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type Incident struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type IncidentsCall struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type Setting struct {
|
||||
Name string `json:"name"`
|
||||
UpdatedBy *int32 `json:"updated_by"`
|
||||
Value []byte `json:"value"`
|
||||
Name string `json:"name,omitempty"`
|
||||
UpdatedBy *int32 `json:"updated_by,omitempty"`
|
||||
Value []byte `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type Share struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type SweptCall struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type System struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ID int `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type Talkgroup struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type TalkgroupVersion struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"dynatron.me/x/stillbox/internal/isoweek"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
@ -135,7 +135,7 @@ func New(db database.Store, cfg config.Partition) (*partman, error) {
|
|||
var _ PartitionManager = (*partman)(nil)
|
||||
|
||||
func (pm *partman) Go(ctx context.Context) {
|
||||
ctx = rbac.CtxWithSubject(ctx, &rbac.SystemServiceSubject{Name: "partman"})
|
||||
ctx = entities.CtxWithSubject(ctx, &entities.SystemServiceSubject{Name: "partman"})
|
||||
tick := time.NewTicker(CheckInterval)
|
||||
|
||||
select {
|
||||
|
|
|
@ -40,6 +40,7 @@ type Querier interface {
|
|||
GetIncident(ctx context.Context, id uuid.UUID) (Incident, error)
|
||||
GetIncidentCalls(ctx context.Context, id uuid.UUID) ([]GetIncidentCallsRow, error)
|
||||
GetIncidentOwner(ctx context.Context, id uuid.UUID) (int, error)
|
||||
GetIncidentTalkgroups(ctx context.Context, incidentID uuid.UUID) ([]GetIncidentTalkgroupsRow, error)
|
||||
GetShare(ctx context.Context, id string) (Share, error)
|
||||
GetSystemName(ctx context.Context, systemID int) (string, error)
|
||||
GetTalkgroup(ctx context.Context, systemID int32, tGID int32) (GetTalkgroupRow, error)
|
||||
|
|
|
@ -53,6 +53,40 @@ func (q *Queries) DeleteShare(ctx context.Context, id string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
const getIncidentTalkgroups = `-- name: GetIncidentTalkgroups :many
|
||||
SELECT DISTINCT
|
||||
c.system,
|
||||
c.talkgroup
|
||||
FROM incidents_calls ic
|
||||
JOIN calls c ON (c.id = ic.call_id AND c.call_date = ic.call_date)
|
||||
WHERE ic.incident_id = $1
|
||||
`
|
||||
|
||||
type GetIncidentTalkgroupsRow struct {
|
||||
System int `json:"system"`
|
||||
Talkgroup int `json:"talkgroup"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetIncidentTalkgroups(ctx context.Context, incidentID uuid.UUID) ([]GetIncidentTalkgroupsRow, error) {
|
||||
rows, err := q.db.Query(ctx, getIncidentTalkgroups, incidentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetIncidentTalkgroupsRow
|
||||
for rows.Next() {
|
||||
var i GetIncidentTalkgroupsRow
|
||||
if err := rows.Scan(&i.System, &i.Talkgroup); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getShare = `-- name: GetShare :one
|
||||
SELECT
|
||||
id,
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
@ -23,7 +23,7 @@ type Incident struct {
|
|||
}
|
||||
|
||||
func (inc *Incident) GetResourceName() string {
|
||||
return rbac.ResourceIncident
|
||||
return entities.ResourceIncident
|
||||
}
|
||||
|
||||
type IncidentCall struct {
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/incidents"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
@ -143,7 +144,7 @@ func (s *store) AddRemoveIncidentCalls(ctx context.Context, incidentID uuid.UUID
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = rbac.Check(ctx, &inc, rbac.WithActions(rbac.ActionUpdate))
|
||||
_, err = rbac.Check(ctx, &inc, rbac.WithActions(entities.ActionUpdate))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -176,7 +177,7 @@ func (s *store) AddRemoveIncidentCalls(ctx context.Context, incidentID uuid.UUID
|
|||
}
|
||||
|
||||
func (s *store) Incidents(ctx context.Context, p IncidentsParams) (incs []Incident, totalCount int, err error) {
|
||||
_, err = rbac.Check(ctx, new(incidents.Incident), rbac.WithActions(rbac.ActionRead))
|
||||
_, err = rbac.Check(ctx, new(incidents.Incident), rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
@ -281,7 +282,7 @@ func fromDBCalls(d []database.GetIncidentCallsRow) []incidents.IncidentCall {
|
|||
}
|
||||
|
||||
func (s *store) Incident(ctx context.Context, id uuid.UUID) (*incidents.Incident, error) {
|
||||
_, err := rbac.Check(ctx, &incidents.Incident{ID: id}, rbac.WithActions(rbac.ActionRead))
|
||||
_, err := rbac.Check(ctx, &incidents.Incident{ID: id}, rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -337,7 +338,7 @@ func (s *store) UpdateIncident(ctx context.Context, id uuid.UUID, p UpdateIncide
|
|||
return nil, err
|
||||
}
|
||||
|
||||
_, err = rbac.Check(ctx, &ckinc, rbac.WithActions(rbac.ActionUpdate))
|
||||
_, err = rbac.Check(ctx, &ckinc, rbac.WithActions(entities.ActionUpdate))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -360,7 +361,7 @@ func (s *store) DeleteIncident(ctx context.Context, id uuid.UUID) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = rbac.Check(ctx, &inc, rbac.WithActions(rbac.ActionDelete))
|
||||
_, err = rbac.Check(ctx, &inc, rbac.WithActions(entities.ActionDelete))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/pb"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
@ -39,7 +39,7 @@ func New() *Nexus {
|
|||
}
|
||||
|
||||
func (n *Nexus) Go(ctx context.Context) {
|
||||
ctx = rbac.CtxWithSubject(ctx, &rbac.SystemServiceSubject{Name: "nexus"})
|
||||
ctx = entities.CtxWithSubject(ctx, &entities.SystemServiceSubject{Name: "nexus"})
|
||||
for {
|
||||
select {
|
||||
case call, ok := <-n.callCh:
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
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"
|
||||
)
|
79
pkg/rbac/entities/entities.go
Normal file
79
pkg/rbac/entities/entities.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package entities
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/el-mike/restrict/v2"
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func SubjectFrom(ctx context.Context) Subject {
|
||||
sub, ok := ctx.Value(SubjectCtxKey).(Subject)
|
||||
if ok {
|
||||
return sub
|
||||
}
|
||||
|
||||
return new(PublicSubject)
|
||||
}
|
||||
|
||||
type Subject interface {
|
||||
restrict.Subject
|
||||
GetName() string
|
||||
}
|
||||
|
||||
func CtxWithSubject(ctx context.Context, sub Subject) context.Context {
|
||||
return context.WithValue(ctx, SubjectCtxKey, sub)
|
||||
}
|
||||
|
||||
type subjectContextKey string
|
||||
|
||||
const SubjectCtxKey subjectContextKey = "sub"
|
||||
|
||||
type Resource interface {
|
||||
restrict.Resource
|
||||
}
|
||||
|
||||
type PublicSubject struct {
|
||||
RemoteAddr string
|
||||
}
|
||||
|
||||
func (s *PublicSubject) GetName() string {
|
||||
return "PUBLIC:" + s.RemoteAddr
|
||||
}
|
||||
|
||||
func (s *PublicSubject) GetRoles() []string {
|
||||
return []string{RolePublic}
|
||||
}
|
||||
|
||||
type SystemServiceSubject struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (s *SystemServiceSubject) GetName() string {
|
||||
return "SYSTEM:" + s.Name
|
||||
}
|
||||
|
||||
func (s *SystemServiceSubject) GetRoles() []string {
|
||||
return []string{RoleSystem}
|
||||
}
|
|
@ -5,9 +5,11 @@ package mocks
|
|||
import (
|
||||
context "context"
|
||||
|
||||
rbac "dynatron.me/x/stillbox/pkg/rbac"
|
||||
entities "dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
rbac "dynatron.me/x/stillbox/pkg/rbac"
|
||||
|
||||
restrict "github.com/el-mike/restrict/v2"
|
||||
)
|
||||
|
||||
|
@ -25,7 +27,7 @@ func (_m *RBAC) EXPECT() *RBAC_Expecter {
|
|||
}
|
||||
|
||||
// Check provides a mock function with given fields: ctx, res, opts
|
||||
func (_m *RBAC) Check(ctx context.Context, res restrict.Resource, opts ...rbac.CheckOption) (rbac.Subject, error) {
|
||||
func (_m *RBAC) Check(ctx context.Context, res restrict.Resource, opts ...rbac.CheckOption) (entities.Subject, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
|
@ -39,16 +41,16 @@ func (_m *RBAC) Check(ctx context.Context, res restrict.Resource, opts ...rbac.C
|
|||
panic("no return value specified for Check")
|
||||
}
|
||||
|
||||
var r0 rbac.Subject
|
||||
var r0 entities.Subject
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, restrict.Resource, ...rbac.CheckOption) (rbac.Subject, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, restrict.Resource, ...rbac.CheckOption) (entities.Subject, error)); ok {
|
||||
return rf(ctx, res, opts...)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, restrict.Resource, ...rbac.CheckOption) rbac.Subject); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, restrict.Resource, ...rbac.CheckOption) entities.Subject); ok {
|
||||
r0 = rf(ctx, res, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(rbac.Subject)
|
||||
r0 = ret.Get(0).(entities.Subject)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,12 +90,12 @@ func (_c *RBAC_Check_Call) Run(run func(ctx context.Context, res restrict.Resour
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *RBAC_Check_Call) Return(_a0 rbac.Subject, _a1 error) *RBAC_Check_Call {
|
||||
func (_c *RBAC_Check_Call) Return(_a0 entities.Subject, _a1 error) *RBAC_Check_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *RBAC_Check_Call) RunAndReturn(run func(context.Context, restrict.Resource, ...rbac.CheckOption) (rbac.Subject, error)) *RBAC_Check_Call {
|
||||
func (_c *RBAC_Check_Call) RunAndReturn(run func(context.Context, restrict.Resource, ...rbac.CheckOption) (entities.Subject, error)) *RBAC_Check_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package rbac
|
||||
package policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||||
|
||||
"github.com/el-mike/restrict/v2"
|
||||
"github.com/google/uuid"
|
||||
|
@ -54,7 +54,7 @@ func (c *CallInIncidentCondition) Check(r *restrict.AccessRequest) error {
|
|||
return restrict.NewConditionNotSatisfiedError(c, r, errors.New("call ID is not UUID"))
|
||||
}
|
||||
|
||||
inCall, err := database.FromCtx(ctx).CallInIncident(ctx, incID, callID)
|
||||
inCall, err := incstore.FromCtx(ctx).CallIn(ctx, incID, callID)
|
||||
if err != nil {
|
||||
return restrict.NewConditionNotSatisfiedError(c, r, err)
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package rbac
|
||||
package policy
|
||||
|
||||
import (
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
|
||||
"github.com/el-mike/restrict/v2"
|
||||
)
|
||||
|
||||
|
@ -19,90 +21,90 @@ const (
|
|||
|
||||
var Policy = &restrict.PolicyDefinition{
|
||||
Roles: restrict.Roles{
|
||||
RoleUser: {
|
||||
entities.RoleUser: {
|
||||
Description: "An authenticated user",
|
||||
Grants: restrict.GrantsMap{
|
||||
ResourceIncident: {
|
||||
&restrict.Permission{Action: ActionRead},
|
||||
&restrict.Permission{Action: ActionCreate},
|
||||
entities.ResourceIncident: {
|
||||
&restrict.Permission{Action: entities.ActionRead},
|
||||
&restrict.Permission{Action: entities.ActionCreate},
|
||||
&restrict.Permission{Preset: PresetUpdateOwn},
|
||||
&restrict.Permission{Preset: PresetDeleteOwn},
|
||||
&restrict.Permission{Preset: PresetShareOwn},
|
||||
},
|
||||
ResourceCall: {
|
||||
&restrict.Permission{Action: ActionRead},
|
||||
&restrict.Permission{Action: ActionCreate},
|
||||
entities.ResourceCall: {
|
||||
&restrict.Permission{Action: entities.ActionRead},
|
||||
&restrict.Permission{Action: entities.ActionCreate},
|
||||
&restrict.Permission{Preset: PresetUpdateSubmitter},
|
||||
&restrict.Permission{Preset: PresetDeleteSubmitter},
|
||||
&restrict.Permission{Action: ActionShare},
|
||||
&restrict.Permission{Action: entities.ActionShare},
|
||||
},
|
||||
ResourceTalkgroup: {
|
||||
&restrict.Permission{Action: ActionRead},
|
||||
entities.ResourceTalkgroup: {
|
||||
&restrict.Permission{Action: entities.ActionRead},
|
||||
},
|
||||
ResourceShare: {
|
||||
&restrict.Permission{Action: ActionRead},
|
||||
&restrict.Permission{Action: ActionCreate},
|
||||
entities.ResourceShare: {
|
||||
&restrict.Permission{Action: entities.ActionRead},
|
||||
&restrict.Permission{Action: entities.ActionCreate},
|
||||
&restrict.Permission{Preset: PresetUpdateOwn},
|
||||
&restrict.Permission{Preset: PresetDeleteOwn},
|
||||
},
|
||||
},
|
||||
},
|
||||
RoleSubmitter: {
|
||||
entities.RoleSubmitter: {
|
||||
Description: "A role that can submit calls",
|
||||
Grants: restrict.GrantsMap{
|
||||
ResourceCall: {
|
||||
&restrict.Permission{Action: ActionCreate},
|
||||
entities.ResourceCall: {
|
||||
&restrict.Permission{Action: entities.ActionCreate},
|
||||
},
|
||||
ResourceTalkgroup: {
|
||||
entities.ResourceTalkgroup: {
|
||||
// for learning TGs
|
||||
&restrict.Permission{Action: ActionCreate},
|
||||
&restrict.Permission{Action: ActionUpdate},
|
||||
&restrict.Permission{Action: entities.ActionCreate},
|
||||
&restrict.Permission{Action: entities.ActionUpdate},
|
||||
},
|
||||
},
|
||||
},
|
||||
RoleShareGuest: {
|
||||
entities.RoleShareGuest: {
|
||||
Description: "Someone who has a valid share link",
|
||||
Grants: restrict.GrantsMap{
|
||||
ResourceCall: {
|
||||
entities.ResourceCall: {
|
||||
&restrict.Permission{Preset: PresetReadShared},
|
||||
&restrict.Permission{Preset: PresetReadInSharedIncident},
|
||||
},
|
||||
ResourceIncident: {
|
||||
entities.ResourceIncident: {
|
||||
&restrict.Permission{Preset: PresetReadShared},
|
||||
},
|
||||
ResourceTalkgroup: {
|
||||
&restrict.Permission{Action: ActionRead},
|
||||
entities.ResourceTalkgroup: {
|
||||
&restrict.Permission{Action: entities.ActionRead},
|
||||
},
|
||||
},
|
||||
},
|
||||
RoleAdmin: {
|
||||
Parents: []string{RoleUser},
|
||||
entities.RoleAdmin: {
|
||||
Parents: []string{entities.RoleUser},
|
||||
Grants: restrict.GrantsMap{
|
||||
ResourceIncident: {
|
||||
&restrict.Permission{Action: ActionUpdate},
|
||||
&restrict.Permission{Action: ActionDelete},
|
||||
&restrict.Permission{Action: ActionShare},
|
||||
entities.ResourceIncident: {
|
||||
&restrict.Permission{Action: entities.ActionUpdate},
|
||||
&restrict.Permission{Action: entities.ActionDelete},
|
||||
&restrict.Permission{Action: entities.ActionShare},
|
||||
},
|
||||
ResourceCall: {
|
||||
&restrict.Permission{Action: ActionUpdate},
|
||||
&restrict.Permission{Action: ActionDelete},
|
||||
&restrict.Permission{Action: ActionShare},
|
||||
entities.ResourceCall: {
|
||||
&restrict.Permission{Action: entities.ActionUpdate},
|
||||
&restrict.Permission{Action: entities.ActionDelete},
|
||||
&restrict.Permission{Action: entities.ActionShare},
|
||||
},
|
||||
ResourceTalkgroup: {
|
||||
&restrict.Permission{Action: ActionUpdate},
|
||||
&restrict.Permission{Action: ActionCreate},
|
||||
&restrict.Permission{Action: ActionDelete},
|
||||
entities.ResourceTalkgroup: {
|
||||
&restrict.Permission{Action: entities.ActionUpdate},
|
||||
&restrict.Permission{Action: entities.ActionCreate},
|
||||
&restrict.Permission{Action: entities.ActionDelete},
|
||||
},
|
||||
},
|
||||
},
|
||||
RoleSystem: {
|
||||
Parents: []string{RoleSystem},
|
||||
entities.RoleSystem: {
|
||||
Parents: []string{entities.RoleSystem},
|
||||
},
|
||||
RolePublic: {
|
||||
entities.RolePublic: {
|
||||
/*
|
||||
Grants: restrict.GrantsMap{
|
||||
ResourceShare: {
|
||||
&restrict.Permission{Action: ActionRead},
|
||||
entities.ResourceShare: {
|
||||
&restrict.Permission{Action: entities.ActionRead},
|
||||
},
|
||||
},
|
||||
*/
|
||||
|
@ -110,7 +112,7 @@ var Policy = &restrict.PolicyDefinition{
|
|||
},
|
||||
PermissionPresets: restrict.PermissionPresets{
|
||||
PresetUpdateOwn: &restrict.Permission{
|
||||
Action: ActionUpdate,
|
||||
Action: entities.ActionUpdate,
|
||||
Conditions: restrict.Conditions{
|
||||
&restrict.EqualCondition{
|
||||
ID: "isOwner",
|
||||
|
@ -126,7 +128,7 @@ var Policy = &restrict.PolicyDefinition{
|
|||
},
|
||||
},
|
||||
PresetDeleteOwn: &restrict.Permission{
|
||||
Action: ActionDelete,
|
||||
Action: entities.ActionDelete,
|
||||
Conditions: restrict.Conditions{
|
||||
&restrict.EqualCondition{
|
||||
ID: "isOwner",
|
||||
|
@ -142,7 +144,7 @@ var Policy = &restrict.PolicyDefinition{
|
|||
},
|
||||
},
|
||||
PresetShareOwn: &restrict.Permission{
|
||||
Action: ActionShare,
|
||||
Action: entities.ActionShare,
|
||||
Conditions: restrict.Conditions{
|
||||
&restrict.EqualCondition{
|
||||
ID: "isOwner",
|
||||
|
@ -158,7 +160,7 @@ var Policy = &restrict.PolicyDefinition{
|
|||
},
|
||||
},
|
||||
PresetUpdateSubmitter: &restrict.Permission{
|
||||
Action: ActionUpdate,
|
||||
Action: entities.ActionUpdate,
|
||||
Conditions: restrict.Conditions{
|
||||
&SubmitterEqualCondition{
|
||||
ID: "isSubmitter",
|
||||
|
@ -174,7 +176,7 @@ var Policy = &restrict.PolicyDefinition{
|
|||
},
|
||||
},
|
||||
PresetDeleteSubmitter: &restrict.Permission{
|
||||
Action: ActionDelete,
|
||||
Action: entities.ActionDelete,
|
||||
Conditions: restrict.Conditions{
|
||||
&SubmitterEqualCondition{
|
||||
ID: "isSubmitter",
|
||||
|
@ -190,7 +192,7 @@ var Policy = &restrict.PolicyDefinition{
|
|||
},
|
||||
},
|
||||
PresetShareSubmitter: &restrict.Permission{
|
||||
Action: ActionShare,
|
||||
Action: entities.ActionShare,
|
||||
Conditions: restrict.Conditions{
|
||||
&SubmitterEqualCondition{
|
||||
ID: "isSubmitter",
|
||||
|
@ -206,7 +208,7 @@ var Policy = &restrict.PolicyDefinition{
|
|||
},
|
||||
},
|
||||
PresetReadShared: &restrict.Permission{
|
||||
Action: ActionRead,
|
||||
Action: entities.ActionRead,
|
||||
Conditions: restrict.Conditions{
|
||||
&restrict.EqualCondition{
|
||||
ID: "isOwner",
|
||||
|
@ -222,7 +224,7 @@ var Policy = &restrict.PolicyDefinition{
|
|||
},
|
||||
},
|
||||
PresetReadInSharedIncident: &restrict.Permission{
|
||||
Action: ActionRead,
|
||||
Action: entities.ActionRead,
|
||||
Conditions: restrict.Conditions{
|
||||
&CallInIncidentCondition{
|
||||
ID: "callInIncident",
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
|
||||
"github.com/el-mike/restrict/v2"
|
||||
"github.com/el-mike/restrict/v2/adapters"
|
||||
)
|
||||
|
@ -12,14 +14,6 @@ var (
|
|||
ErrBadSubject = errors.New("bad subject in token")
|
||||
)
|
||||
|
||||
type subjectContextKey string
|
||||
|
||||
const SubjectCtxKey subjectContextKey = "sub"
|
||||
|
||||
func CtxWithSubject(ctx context.Context, sub Subject) context.Context {
|
||||
return context.WithValue(ctx, SubjectCtxKey, sub)
|
||||
}
|
||||
|
||||
func ErrAccessDenied(err error) *restrict.AccessDeniedError {
|
||||
if accessErr, ok := err.(*restrict.AccessDeniedError); ok {
|
||||
return accessErr
|
||||
|
@ -28,15 +22,6 @@ func ErrAccessDenied(err error) *restrict.AccessDeniedError {
|
|||
return nil
|
||||
}
|
||||
|
||||
func SubjectFrom(ctx context.Context) Subject {
|
||||
sub, ok := ctx.Value(SubjectCtxKey).(Subject)
|
||||
if ok {
|
||||
return sub
|
||||
}
|
||||
|
||||
return new(PublicSubject)
|
||||
}
|
||||
|
||||
type rbacCtxKey string
|
||||
|
||||
const RBACCtxKey rbacCtxKey = "rbac"
|
||||
|
@ -81,17 +66,8 @@ func UseResource(rsc string) restrict.Resource {
|
|||
return restrict.UseResource(rsc)
|
||||
}
|
||||
|
||||
type Subject interface {
|
||||
restrict.Subject
|
||||
GetName() string
|
||||
}
|
||||
|
||||
type Resource interface {
|
||||
restrict.Resource
|
||||
}
|
||||
|
||||
type RBAC interface {
|
||||
Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (Subject, error)
|
||||
Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (entities.Subject, error)
|
||||
}
|
||||
|
||||
type rbac struct {
|
||||
|
@ -99,8 +75,8 @@ type rbac struct {
|
|||
access *restrict.AccessManager
|
||||
}
|
||||
|
||||
func New() (*rbac, error) {
|
||||
adapter := adapters.NewInMemoryAdapter(Policy)
|
||||
func New(pol *restrict.PolicyDefinition) (*rbac, error) {
|
||||
adapter := adapters.NewInMemoryAdapter(pol)
|
||||
polMan, err := restrict.NewPolicyManager(adapter, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -114,12 +90,12 @@ func New() (*rbac, error) {
|
|||
}
|
||||
|
||||
// Check is a convenience function to pull the RBAC instance out of ctx and Check.
|
||||
func Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (Subject, error) {
|
||||
func Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (entities.Subject, error) {
|
||||
return FromCtx(ctx).Check(ctx, res, opts...)
|
||||
}
|
||||
|
||||
func (r *rbac) Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (Subject, error) {
|
||||
sub := SubjectFrom(ctx)
|
||||
func (r *rbac) Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (entities.Subject, error) {
|
||||
sub := entities.SubjectFrom(ctx)
|
||||
o := checkOptions{}
|
||||
|
||||
for _, opt := range opts {
|
||||
|
@ -139,34 +115,5 @@ func (r *rbac) Check(ctx context.Context, res restrict.Resource, opts ...CheckOp
|
|||
Context: o.context,
|
||||
}
|
||||
|
||||
err := r.access.Authorize(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
type PublicSubject struct {
|
||||
RemoteAddr string
|
||||
}
|
||||
|
||||
func (s *PublicSubject) GetName() string {
|
||||
return "PUBLIC:" + s.RemoteAddr
|
||||
}
|
||||
|
||||
func (s *PublicSubject) GetRoles() []string {
|
||||
return []string{RolePublic}
|
||||
}
|
||||
|
||||
type SystemServiceSubject struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (s *SystemServiceSubject) GetName() string {
|
||||
return "SYSTEM:" + s.Name
|
||||
}
|
||||
|
||||
func (s *SystemServiceSubject) GetRoles() []string {
|
||||
return []string{RoleSystem}
|
||||
return sub, r.access.Authorize(req)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/incidents"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/policy"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
"github.com/el-mike/restrict/v2"
|
||||
|
@ -20,8 +22,8 @@ import (
|
|||
func TestRBAC(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
subject rbac.Subject
|
||||
resource rbac.Resource
|
||||
subject entities.Subject
|
||||
resource entities.Resource
|
||||
action string
|
||||
expectErr error
|
||||
}{
|
||||
|
@ -32,7 +34,7 @@ func TestRBAC(t *testing.T) {
|
|||
IsAdmin: true,
|
||||
},
|
||||
resource: &talkgroups.Talkgroup{},
|
||||
action: rbac.ActionUpdate,
|
||||
action: entities.ActionUpdate,
|
||||
expectErr: nil,
|
||||
},
|
||||
{
|
||||
|
@ -45,7 +47,7 @@ func TestRBAC(t *testing.T) {
|
|||
Name: "test incident",
|
||||
Owner: 4,
|
||||
},
|
||||
action: rbac.ActionUpdate,
|
||||
action: entities.ActionUpdate,
|
||||
expectErr: nil,
|
||||
},
|
||||
{
|
||||
|
@ -57,7 +59,7 @@ func TestRBAC(t *testing.T) {
|
|||
Name: "test incident",
|
||||
Owner: 4,
|
||||
},
|
||||
action: rbac.ActionUpdate,
|
||||
action: entities.ActionUpdate,
|
||||
expectErr: errors.New(`access denied for Action: "update" on Resource: "Incident"`),
|
||||
},
|
||||
{
|
||||
|
@ -69,7 +71,7 @@ func TestRBAC(t *testing.T) {
|
|||
Name: "test incident",
|
||||
Owner: 2,
|
||||
},
|
||||
action: rbac.ActionUpdate,
|
||||
action: entities.ActionUpdate,
|
||||
expectErr: nil,
|
||||
},
|
||||
{
|
||||
|
@ -81,7 +83,7 @@ func TestRBAC(t *testing.T) {
|
|||
Name: "test incident",
|
||||
Owner: 6,
|
||||
},
|
||||
action: rbac.ActionDelete,
|
||||
action: entities.ActionDelete,
|
||||
expectErr: errors.New(`access denied for Action: "delete" on Resource: "Incident"`),
|
||||
},
|
||||
{
|
||||
|
@ -93,7 +95,7 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &calls.Call{
|
||||
Submitter: common.PtrTo(users.UserID(4)),
|
||||
},
|
||||
action: rbac.ActionUpdate,
|
||||
action: entities.ActionUpdate,
|
||||
expectErr: nil,
|
||||
},
|
||||
{
|
||||
|
@ -104,7 +106,7 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &calls.Call{
|
||||
Submitter: common.PtrTo(users.UserID(4)),
|
||||
},
|
||||
action: rbac.ActionUpdate,
|
||||
action: entities.ActionUpdate,
|
||||
expectErr: errors.New(`access denied for Action: "update" on Resource: "Call"`),
|
||||
},
|
||||
{
|
||||
|
@ -115,7 +117,7 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &calls.Call{
|
||||
Submitter: common.PtrTo(users.UserID(2)),
|
||||
},
|
||||
action: rbac.ActionUpdate,
|
||||
action: entities.ActionUpdate,
|
||||
expectErr: nil,
|
||||
},
|
||||
{
|
||||
|
@ -126,7 +128,7 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &calls.Call{
|
||||
Submitter: nil,
|
||||
},
|
||||
action: rbac.ActionUpdate,
|
||||
action: entities.ActionUpdate,
|
||||
expectErr: errors.New(`access denied for Action: "update" on Resource: "Call"`),
|
||||
},
|
||||
{
|
||||
|
@ -137,7 +139,7 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &calls.Call{
|
||||
Submitter: common.PtrTo(users.UserID(6)),
|
||||
},
|
||||
action: rbac.ActionDelete,
|
||||
action: entities.ActionDelete,
|
||||
expectErr: errors.New(`access denied for Action: "delete" on Resource: "Call"`),
|
||||
},
|
||||
{
|
||||
|
@ -148,7 +150,7 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &calls.Call{
|
||||
Submitter: common.PtrTo(users.UserID(6)),
|
||||
},
|
||||
action: rbac.ActionShare,
|
||||
action: entities.ActionShare,
|
||||
expectErr: nil,
|
||||
},
|
||||
{
|
||||
|
@ -160,7 +162,7 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &calls.Call{
|
||||
Submitter: common.PtrTo(users.UserID(6)),
|
||||
},
|
||||
action: rbac.ActionShare,
|
||||
action: entities.ActionShare,
|
||||
expectErr: nil,
|
||||
},
|
||||
{
|
||||
|
@ -171,7 +173,7 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &calls.Call{
|
||||
Submitter: common.PtrTo(users.UserID(6)),
|
||||
},
|
||||
action: rbac.ActionShare,
|
||||
action: entities.ActionShare,
|
||||
expectErr: nil,
|
||||
},
|
||||
{
|
||||
|
@ -182,7 +184,7 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &incidents.Incident{
|
||||
Owner: users.UserID(6),
|
||||
},
|
||||
action: rbac.ActionShare,
|
||||
action: entities.ActionShare,
|
||||
expectErr: errors.New(`access denied for Action: "share" on Resource: "Incident"`),
|
||||
},
|
||||
{
|
||||
|
@ -194,7 +196,7 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &incidents.Incident{
|
||||
Owner: users.UserID(6),
|
||||
},
|
||||
action: rbac.ActionShare,
|
||||
action: entities.ActionShare,
|
||||
expectErr: nil,
|
||||
},
|
||||
{
|
||||
|
@ -205,15 +207,15 @@ func TestRBAC(t *testing.T) {
|
|||
resource: &incidents.Incident{
|
||||
Owner: users.UserID(6),
|
||||
},
|
||||
action: rbac.ActionShare,
|
||||
action: entities.ActionShare,
|
||||
expectErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := rbac.CtxWithSubject(context.Background(), tc.subject)
|
||||
rb, err := rbac.New()
|
||||
ctx := entities.CtxWithSubject(context.Background(), tc.subject)
|
||||
rb, err := rbac.New(policy.Policy)
|
||||
require.NoError(t, err)
|
||||
sub, err := rb.Check(ctx, tc.resource, rbac.WithActions(tc.action))
|
||||
if tc.expectErr != nil {
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/forms"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/shares"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
@ -133,7 +133,7 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx = rbac.CtxWithSubject(ctx, sh)
|
||||
ctx = entities.CtxWithSubject(ctx, sh)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
if params.SubType != nil {
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/nexus"
|
||||
"dynatron.me/x/stillbox/pkg/notify"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/policy"
|
||||
"dynatron.me/x/stillbox/pkg/rest"
|
||||
"dynatron.me/x/stillbox/pkg/shares"
|
||||
"dynatron.me/x/stillbox/pkg/sinks"
|
||||
|
@ -80,7 +81,7 @@ func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
|||
tgCache := tgstore.NewCache(db)
|
||||
api := rest.New(cfg.BaseURL.URL())
|
||||
|
||||
rbacSvc, err := rbac.New()
|
||||
rbacSvc, err := rbac.New(policy.Policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
|
@ -23,7 +23,7 @@ type service struct {
|
|||
}
|
||||
|
||||
func (s *service) Go(ctx context.Context) {
|
||||
ctx = rbac.CtxWithSubject(ctx, &rbac.SystemServiceSubject{Name: "share"})
|
||||
ctx = entities.CtxWithSubject(ctx, &entities.SystemServiceSubject{Name: "share"})
|
||||
|
||||
tick := time.NewTicker(PruneInterval)
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/calls/callstore"
|
||||
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
@ -57,11 +58,11 @@ func (s *Share) GetName() string {
|
|||
}
|
||||
|
||||
func (s *Share) GetRoles() []string {
|
||||
return []string{rbac.RoleShareGuest}
|
||||
return []string{entities.RoleShareGuest}
|
||||
}
|
||||
|
||||
func (s *Share) GetResourceName() string {
|
||||
return rbac.ResourceShare
|
||||
return entities.ResourceShare
|
||||
}
|
||||
|
||||
type CreateShareParams struct {
|
||||
|
@ -88,7 +89,7 @@ func (s *service) checkEntity(ctx context.Context, sh *CreateShareParams) (*time
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = rbac.Check(ctx, &i, rbac.WithActions(rbac.ActionShare))
|
||||
_, err = rbac.Check(ctx, &i, rbac.WithActions(entities.ActionShare))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
@ -79,7 +80,7 @@ func (s *postgresStore) Create(ctx context.Context, share *Share) error {
|
|||
}
|
||||
|
||||
func (s *postgresStore) Delete(ctx context.Context, id string) error {
|
||||
_, err := rbac.Check(ctx, new(Share), rbac.WithActions(rbac.ActionDelete))
|
||||
_, err := rbac.Check(ctx, new(Share), rbac.WithActions(entities.ActionDelete))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/auth"
|
||||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
@ -131,7 +132,7 @@ func (h *RdioHTTP) routeCallUpload(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = h.ing.Ingest(rbac.CtxWithSubject(ctx, submitterSub), call)
|
||||
err = h.ing.Ingest(entities.CtxWithSubject(ctx, submitterSub), call)
|
||||
if err != nil {
|
||||
if rbac.ErrAccessDenied(err) != nil {
|
||||
log.Error().Err(err).Msg("ingest failed")
|
||||
|
|
|
@ -122,6 +122,7 @@ func (f *TalkgroupFilter) compile(ctx context.Context) error {
|
|||
|
||||
if f.hasTags() { // don't bother with DB if no tags
|
||||
db := database.FromCtx(ctx)
|
||||
// TODO: change this to use tgstore, and make sure the context is no longer a system subject (see nexus.Go)
|
||||
tagTGs, err := db.GetTalkgroupIDsByTags(ctx, f.TalkgroupTagsAny, f.TalkgroupTagsAll, f.TalkgroupTagsNot)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
)
|
||||
|
||||
type Talkgroup struct {
|
||||
|
@ -19,7 +19,7 @@ type Talkgroup struct {
|
|||
}
|
||||
|
||||
func (t *Talkgroup) GetResourceName() string {
|
||||
return rbac.ResourceTalkgroup
|
||||
return entities.ResourceTalkgroup
|
||||
}
|
||||
|
||||
func (t Talkgroup) String() string {
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
tgsp "dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
|
||||
|
@ -327,7 +328,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(entities.ResourceTalkgroup), rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -430,7 +431,7 @@ func (t *cache) Weight(ctx context.Context, id tgsp.ID, tm time.Time) float64 {
|
|||
}
|
||||
|
||||
func (t *cache) SystemTGs(ctx context.Context, systemID int, opts ...Option) ([]*tgsp.Talkgroup, error) {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(rbac.ResourceTalkgroup), rbac.WithActions(rbac.ActionRead))
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceTalkgroup), rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -486,7 +487,7 @@ func (t *cache) SystemTGs(ctx context.Context, systemID int, opts ...Option) ([]
|
|||
}
|
||||
|
||||
func (t *cache) TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error) {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(rbac.ResourceTalkgroup), rbac.WithActions(rbac.ActionRead))
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceTalkgroup), rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -513,7 +514,7 @@ func (t *cache) TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error) {
|
|||
}
|
||||
|
||||
func (t *cache) SystemName(ctx context.Context, id int) (name string, has bool) {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(rbac.ResourceTalkgroup), rbac.WithActions(rbac.ActionRead))
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceTalkgroup), rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
@ -587,7 +588,7 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara
|
|||
}
|
||||
|
||||
func (t *cache) DeleteSystem(ctx context.Context, id int) error {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(rbac.ResourceTalkgroup), rbac.WithActions(rbac.ActionDelete))
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceTalkgroup), rbac.WithActions(entities.ActionDelete))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -609,7 +610,7 @@ func (t *cache) DeleteSystem(ctx context.Context, id int) error {
|
|||
}
|
||||
|
||||
func (t *cache) DeleteTG(ctx context.Context, id tgsp.ID) error {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(rbac.ResourceTalkgroup), rbac.WithActions(rbac.ActionDelete))
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceTalkgroup), rbac.WithActions(entities.ActionDelete))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -645,7 +646,7 @@ func (t *cache) DeleteTG(ctx context.Context, id tgsp.ID) error {
|
|||
}
|
||||
|
||||
func (t *cache) LearnTG(ctx context.Context, c *calls.Call) (*tgsp.Talkgroup, error) {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(rbac.ResourceTalkgroup), rbac.WithActions(rbac.ActionCreate, rbac.ActionUpdate))
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceTalkgroup), rbac.WithActions(entities.ActionCreate, entities.ActionUpdate))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -764,7 +765,7 @@ func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.Upse
|
|||
}
|
||||
|
||||
func (t *cache) CreateSystem(ctx context.Context, id int, name string) error {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(rbac.ResourceTalkgroup), rbac.WithActions(rbac.ActionCreate))
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceTalkgroup), rbac.WithActions(entities.ActionCreate))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -778,7 +779,7 @@ func (t *cache) CreateSystem(ctx context.Context, id int, name string) error {
|
|||
}
|
||||
|
||||
func (t *cache) Tags(ctx context.Context) ([]string, error) {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(rbac.ResourceTalkgroup), rbac.WithActions(rbac.ActionRead))
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceTalkgroup), rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
)
|
||||
|
||||
type UserID int
|
||||
|
@ -30,11 +31,11 @@ func (u UserID) IsValid() bool {
|
|||
}
|
||||
|
||||
func From(ctx context.Context) (*User, error) {
|
||||
sub := rbac.SubjectFrom(ctx)
|
||||
sub := entities.SubjectFrom(ctx)
|
||||
return FromSubject(sub)
|
||||
}
|
||||
|
||||
func UserCheck(ctx context.Context, rsc rbac.Resource, actions string) (*User, error) {
|
||||
func UserCheck(ctx context.Context, rsc entities.Resource, actions string) (*User, error) {
|
||||
acts := strings.Split(actions, "+")
|
||||
subj, err := rbac.FromCtx(ctx).Check(ctx, rsc, rbac.WithActions(acts...))
|
||||
if err != nil {
|
||||
|
@ -44,7 +45,7 @@ func UserCheck(ctx context.Context, rsc rbac.Resource, actions string) (*User, e
|
|||
return FromSubject(subj)
|
||||
}
|
||||
|
||||
func FromSubject(sub rbac.Subject) (*User, error) {
|
||||
func FromSubject(sub entities.Subject) (*User, error) {
|
||||
if sub == nil {
|
||||
return nil, rbac.ErrBadSubject
|
||||
}
|
||||
|
@ -73,10 +74,10 @@ func (u *User) GetName() string {
|
|||
func (u *User) GetRoles() []string {
|
||||
r := make([]string, 1, 2)
|
||||
|
||||
r[0] = rbac.RoleUser
|
||||
r[0] = entities.RoleUser
|
||||
|
||||
if u.IsAdmin {
|
||||
r = append(r, rbac.RoleAdmin)
|
||||
r = append(r, entities.RoleAdmin)
|
||||
}
|
||||
|
||||
return r
|
||||
|
|
|
@ -24,3 +24,11 @@ DELETE FROM shares WHERE id = @id;
|
|||
|
||||
-- name: PruneShares :exec
|
||||
DELETE FROM shares WHERE expiration < NOW();
|
||||
|
||||
-- name: GetIncidentTalkgroups :many
|
||||
SELECT DISTINCT
|
||||
c.system,
|
||||
c.talkgroup
|
||||
FROM incidents_calls ic
|
||||
JOIN calls c ON (c.id = ic.call_id AND c.call_date = ic.call_date)
|
||||
WHERE ic.incident_id = @incident_id;
|
||||
|
|
Loading…
Reference in a new issue