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/config"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/notify"
|
"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/sinks"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
"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.
|
// Go is the alerting loop. It does not start a goroutine.
|
||||||
func (as *alerter) Go(ctx context.Context) {
|
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)
|
err := as.startBackfill(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"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/google/uuid"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -16,10 +16,10 @@ import (
|
||||||
type apiKeyAuth interface {
|
type apiKeyAuth interface {
|
||||||
// CheckAPIKey validates the provided key and returns the API owner's users.UserID.
|
// CheckAPIKey validates the provided key and returns the API owner's users.UserID.
|
||||||
// An error is returned if validation fails for any reason.
|
// 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)
|
keyUuid, err := uuid.Parse(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Str("apikey", key).Msg("cannot parse key")
|
log.Error().Str("apikey", key).Msg("cannot parse key")
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"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"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
@ -104,7 +104,7 @@ func (a *Auth) AuthMiddleware() func(http.Handler) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = rbac.CtxWithSubject(ctx, sub)
|
ctx = entities.CtxWithSubject(ctx, sub)
|
||||||
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ 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/rbac/entities"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ type Call struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Call) GetResourceName() string {
|
func (c *Call) GetResourceName() string {
|
||||||
return rbac.ResourceCall
|
return entities.ResourceCall
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Call) String() string {
|
func (c *Call) String() string {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"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/talkgroups/tgstore"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
@ -256,7 +257,7 @@ func (s *store) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = rbac.Check(ctx, &callOwn, rbac.WithActions(rbac.ActionDelete))
|
_, err = rbac.Check(ctx, &callOwn, rbac.WithActions(entities.ActionDelete))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -278,6 +278,64 @@ func (_c *Store_BulkSetTalkgroupTags_Call) RunAndReturn(run func(context.Context
|
||||||
return _c
|
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
|
// 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) {
|
func (_m *Store) CleanupSweptCalls(ctx context.Context, rangeStart pgtype.Timestamptz, rangeEnd pgtype.Timestamptz) (int64, error) {
|
||||||
ret := _m.Called(ctx, rangeStart, rangeEnd)
|
ret := _m.Called(ctx, rangeStart, rangeEnd)
|
||||||
|
@ -1750,6 +1808,65 @@ func (_c *Store_GetIncidentOwner_Call) RunAndReturn(run func(context.Context, uu
|
||||||
return _c
|
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
|
// GetShare provides a mock function with given fields: ctx, id
|
||||||
func (_m *Store) GetShare(ctx context.Context, id string) (database.Share, error) {
|
func (_m *Store) GetShare(ctx context.Context, id string) (database.Share, error) {
|
||||||
ret := _m.Called(ctx, id)
|
ret := _m.Called(ctx, id)
|
||||||
|
|
|
@ -14,150 +14,150 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Alert struct {
|
type Alert struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id,omitempty"`
|
||||||
Time pgtype.Timestamptz `json:"time"`
|
Time pgtype.Timestamptz `json:"time,omitempty"`
|
||||||
TGID int `json:"tgid"`
|
TGID int `json:"tgid,omitempty"`
|
||||||
SystemID int `json:"system_id"`
|
SystemID int `json:"system_id,omitempty"`
|
||||||
Weight *float32 `json:"weight"`
|
Weight *float32 `json:"weight,omitempty"`
|
||||||
Score *float32 `json:"score"`
|
Score *float32 `json:"score,omitempty"`
|
||||||
OrigScore *float32 `json:"orig_score"`
|
OrigScore *float32 `json:"orig_score,omitempty"`
|
||||||
Notified bool `json:"notified"`
|
Notified bool `json:"notified,omitempty"`
|
||||||
Metadata []byte `json:"metadata"`
|
Metadata []byte `json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiKey struct {
|
type ApiKey struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id,omitempty"`
|
||||||
Owner int `json:"owner"`
|
Owner int `json:"owner,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
Expires pgtype.Timestamp `json:"expires"`
|
Expires pgtype.Timestamp `json:"expires,omitempty"`
|
||||||
Disabled *bool `json:"disabled"`
|
Disabled *bool `json:"disabled,omitempty"`
|
||||||
ApiKey string `json:"api_key"`
|
ApiKey string `json:"api_key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Call struct {
|
type Call struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id,omitempty"`
|
||||||
Submitter *int32 `json:"submitter"`
|
Submitter *int32 `json:"submitter,omitempty"`
|
||||||
System int `json:"system"`
|
System int `json:"system,omitempty"`
|
||||||
Talkgroup int `json:"talkgroup"`
|
Talkgroup int `json:"talkgroup,omitempty"`
|
||||||
CallDate pgtype.Timestamptz `json:"call_date"`
|
CallDate pgtype.Timestamptz `json:"call_date,omitempty"`
|
||||||
AudioName *string `json:"audio_name"`
|
AudioName *string `json:"audio_name,omitempty"`
|
||||||
AudioBlob []byte `json:"audio_blob"`
|
AudioBlob []byte `json:"audio_blob,omitempty"`
|
||||||
Duration *int32 `json:"duration"`
|
Duration *int32 `json:"duration,omitempty"`
|
||||||
AudioType *string `json:"audio_type"`
|
AudioType *string `json:"audio_type,omitempty"`
|
||||||
AudioUrl *string `json:"audio_url"`
|
AudioUrl *string `json:"audio_url,omitempty"`
|
||||||
Frequency int `json:"frequency"`
|
Frequency int `json:"frequency,omitempty"`
|
||||||
Frequencies []int `json:"frequencies"`
|
Frequencies []int `json:"frequencies,omitempty"`
|
||||||
Patches []int `json:"patches"`
|
Patches []int `json:"patches,omitempty"`
|
||||||
TGLabel *string `json:"tg_label"`
|
TGLabel *string `json:"tg_label,omitempty"`
|
||||||
TGAlphaTag *string `json:"tg_alpha_tag"`
|
TGAlphaTag *string `json:"tg_alpha_tag,omitempty"`
|
||||||
TGGroup *string `json:"tg_group"`
|
TGGroup *string `json:"tg_group,omitempty"`
|
||||||
Source int `json:"source"`
|
Source int `json:"source,omitempty"`
|
||||||
Transcript *string `json:"transcript"`
|
Transcript *string `json:"transcript,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Incident struct {
|
type Incident struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id,omitempty"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name,omitempty"`
|
||||||
Owner int `json:"owner"`
|
Owner int `json:"owner,omitempty"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description,omitempty"`
|
||||||
StartTime pgtype.Timestamptz `json:"start_time"`
|
StartTime pgtype.Timestamptz `json:"start_time,omitempty"`
|
||||||
EndTime pgtype.Timestamptz `json:"end_time"`
|
EndTime pgtype.Timestamptz `json:"end_time,omitempty"`
|
||||||
Location []byte `json:"location"`
|
Location []byte `json:"location,omitempty"`
|
||||||
Metadata jsontypes.Metadata `json:"metadata"`
|
Metadata jsontypes.Metadata `json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IncidentsCall struct {
|
type IncidentsCall struct {
|
||||||
IncidentID uuid.UUID `json:"incident_id"`
|
IncidentID uuid.UUID `json:"incident_id,omitempty"`
|
||||||
CallID uuid.UUID `json:"call_id"`
|
CallID uuid.UUID `json:"call_id,omitempty"`
|
||||||
CallsTblID pgtype.UUID `json:"calls_tbl_id"`
|
CallsTblID pgtype.UUID `json:"calls_tbl_id,omitempty"`
|
||||||
SweptCallID pgtype.UUID `json:"swept_call_id"`
|
SweptCallID pgtype.UUID `json:"swept_call_id,omitempty"`
|
||||||
CallDate pgtype.Timestamptz `json:"call_date"`
|
CallDate pgtype.Timestamptz `json:"call_date,omitempty"`
|
||||||
Notes []byte `json:"notes"`
|
Notes []byte `json:"notes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Setting struct {
|
type Setting struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name,omitempty"`
|
||||||
UpdatedBy *int32 `json:"updated_by"`
|
UpdatedBy *int32 `json:"updated_by,omitempty"`
|
||||||
Value []byte `json:"value"`
|
Value []byte `json:"value,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Share struct {
|
type Share struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id,omitempty"`
|
||||||
EntityType string `json:"entity_type"`
|
EntityType string `json:"entity_type,omitempty"`
|
||||||
EntityID uuid.UUID `json:"entity_id"`
|
EntityID uuid.UUID `json:"entity_id,omitempty"`
|
||||||
EntityDate pgtype.Timestamptz `json:"entity_date"`
|
EntityDate pgtype.Timestamptz `json:"entity_date,omitempty"`
|
||||||
Owner int `json:"owner"`
|
Owner int `json:"owner,omitempty"`
|
||||||
Expiration pgtype.Timestamptz `json:"expiration"`
|
Expiration pgtype.Timestamptz `json:"expiration,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SweptCall struct {
|
type SweptCall struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id,omitempty"`
|
||||||
Submitter *int32 `json:"submitter"`
|
Submitter *int32 `json:"submitter,omitempty"`
|
||||||
System int `json:"system"`
|
System int `json:"system,omitempty"`
|
||||||
Talkgroup int `json:"talkgroup"`
|
Talkgroup int `json:"talkgroup,omitempty"`
|
||||||
CallDate pgtype.Timestamptz `json:"call_date"`
|
CallDate pgtype.Timestamptz `json:"call_date,omitempty"`
|
||||||
AudioName *string `json:"audio_name"`
|
AudioName *string `json:"audio_name,omitempty"`
|
||||||
AudioBlob []byte `json:"audio_blob"`
|
AudioBlob []byte `json:"audio_blob,omitempty"`
|
||||||
Duration *int32 `json:"duration"`
|
Duration *int32 `json:"duration,omitempty"`
|
||||||
AudioType *string `json:"audio_type"`
|
AudioType *string `json:"audio_type,omitempty"`
|
||||||
AudioUrl *string `json:"audio_url"`
|
AudioUrl *string `json:"audio_url,omitempty"`
|
||||||
Frequency int `json:"frequency"`
|
Frequency int `json:"frequency,omitempty"`
|
||||||
Frequencies []int `json:"frequencies"`
|
Frequencies []int `json:"frequencies,omitempty"`
|
||||||
Patches []int `json:"patches"`
|
Patches []int `json:"patches,omitempty"`
|
||||||
TGLabel *string `json:"tg_label"`
|
TGLabel *string `json:"tg_label,omitempty"`
|
||||||
TGAlphaTag *string `json:"tg_alpha_tag"`
|
TGAlphaTag *string `json:"tg_alpha_tag,omitempty"`
|
||||||
TGGroup *string `json:"tg_group"`
|
TGGroup *string `json:"tg_group,omitempty"`
|
||||||
Source int `json:"source"`
|
Source int `json:"source,omitempty"`
|
||||||
Transcript *string `json:"transcript"`
|
Transcript *string `json:"transcript,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type System struct {
|
type System struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id,omitempty"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Talkgroup struct {
|
type Talkgroup struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id,omitempty"`
|
||||||
SystemID int32 `json:"system_id"`
|
SystemID int32 `json:"system_id,omitempty"`
|
||||||
TGID int32 `json:"tgid"`
|
TGID int32 `json:"tgid,omitempty"`
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name,omitempty"`
|
||||||
AlphaTag *string `json:"alpha_tag"`
|
AlphaTag *string `json:"alpha_tag,omitempty"`
|
||||||
TGGroup *string `json:"tg_group"`
|
TGGroup *string `json:"tg_group,omitempty"`
|
||||||
Frequency *int32 `json:"frequency"`
|
Frequency *int32 `json:"frequency,omitempty"`
|
||||||
Metadata jsontypes.Metadata `json:"metadata"`
|
Metadata jsontypes.Metadata `json:"metadata,omitempty"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
Alert bool `json:"alert"`
|
Alert bool `json:"alert,omitempty"`
|
||||||
AlertRules rules.AlertRules `json:"alert_rules"`
|
AlertRules rules.AlertRules `json:"alert_rules,omitempty"`
|
||||||
Weight float32 `json:"weight"`
|
Weight float32 `json:"weight,omitempty"`
|
||||||
Learned bool `json:"learned"`
|
Learned bool `json:"learned,omitempty"`
|
||||||
Ignored bool `json:"ignored"`
|
Ignored bool `json:"ignored,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TalkgroupVersion struct {
|
type TalkgroupVersion struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id,omitempty"`
|
||||||
Time pgtype.Timestamptz `json:"time"`
|
Time pgtype.Timestamptz `json:"time,omitempty"`
|
||||||
CreatedBy *int32 `json:"created_by"`
|
CreatedBy *int32 `json:"created_by,omitempty"`
|
||||||
Deleted *bool `json:"deleted"`
|
Deleted *bool `json:"deleted,omitempty"`
|
||||||
SystemID *int32 `json:"system_id"`
|
SystemID *int32 `json:"system_id,omitempty"`
|
||||||
TGID *int32 `json:"tgid"`
|
TGID *int32 `json:"tgid,omitempty"`
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name,omitempty"`
|
||||||
AlphaTag *string `json:"alpha_tag"`
|
AlphaTag *string `json:"alpha_tag,omitempty"`
|
||||||
TGGroup *string `json:"tg_group"`
|
TGGroup *string `json:"tg_group,omitempty"`
|
||||||
Frequency *int32 `json:"frequency"`
|
Frequency *int32 `json:"frequency,omitempty"`
|
||||||
Metadata []byte `json:"metadata"`
|
Metadata []byte `json:"metadata,omitempty"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
Alert *bool `json:"alert"`
|
Alert *bool `json:"alert,omitempty"`
|
||||||
AlertRules []byte `json:"alert_rules"`
|
AlertRules []byte `json:"alert_rules,omitempty"`
|
||||||
Weight *float32 `json:"weight"`
|
Weight *float32 `json:"weight,omitempty"`
|
||||||
Learned *bool `json:"learned"`
|
Learned *bool `json:"learned,omitempty"`
|
||||||
Ignored *bool `json:"ignored"`
|
Ignored *bool `json:"ignored,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id,omitempty"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password,omitempty"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email,omitempty"`
|
||||||
IsAdmin bool `json:"is_admin"`
|
IsAdmin bool `json:"is_admin,omitempty"`
|
||||||
Prefs []byte `json:"prefs"`
|
Prefs []byte `json:"prefs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/internal/isoweek"
|
"dynatron.me/x/stillbox/internal/isoweek"
|
||||||
"dynatron.me/x/stillbox/pkg/config"
|
"dynatron.me/x/stillbox/pkg/config"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"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"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
@ -135,7 +135,7 @@ func New(db database.Store, cfg config.Partition) (*partman, error) {
|
||||||
var _ PartitionManager = (*partman)(nil)
|
var _ PartitionManager = (*partman)(nil)
|
||||||
|
|
||||||
func (pm *partman) Go(ctx context.Context) {
|
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)
|
tick := time.NewTicker(CheckInterval)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|
|
@ -40,6 +40,7 @@ type Querier interface {
|
||||||
GetIncident(ctx context.Context, id uuid.UUID) (Incident, error)
|
GetIncident(ctx context.Context, id uuid.UUID) (Incident, error)
|
||||||
GetIncidentCalls(ctx context.Context, id uuid.UUID) ([]GetIncidentCallsRow, error)
|
GetIncidentCalls(ctx context.Context, id uuid.UUID) ([]GetIncidentCallsRow, error)
|
||||||
GetIncidentOwner(ctx context.Context, id uuid.UUID) (int, 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)
|
GetShare(ctx context.Context, id string) (Share, error)
|
||||||
GetSystemName(ctx context.Context, systemID int) (string, error)
|
GetSystemName(ctx context.Context, systemID int) (string, error)
|
||||||
GetTalkgroup(ctx context.Context, systemID int32, tGID int32) (GetTalkgroupRow, 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
|
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
|
const getShare = `-- name: GetShare :one
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -5,7 +5,7 @@ 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/rbac/entities"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
@ -23,7 +23,7 @@ type Incident struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inc *Incident) GetResourceName() string {
|
func (inc *Incident) GetResourceName() string {
|
||||||
return rbac.ResourceIncident
|
return entities.ResourceIncident
|
||||||
}
|
}
|
||||||
|
|
||||||
type IncidentCall struct {
|
type IncidentCall struct {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/incidents"
|
"dynatron.me/x/stillbox/pkg/incidents"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
|
@ -143,7 +144,7 @@ func (s *store) AddRemoveIncidentCalls(ctx context.Context, incidentID uuid.UUID
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = rbac.Check(ctx, &inc, rbac.WithActions(rbac.ActionUpdate))
|
_, err = rbac.Check(ctx, &inc, rbac.WithActions(entities.ActionUpdate))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, 0, err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -337,7 +338,7 @@ func (s *store) UpdateIncident(ctx context.Context, id uuid.UUID, p UpdateIncide
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = rbac.Check(ctx, &ckinc, rbac.WithActions(rbac.ActionUpdate))
|
_, err = rbac.Check(ctx, &ckinc, rbac.WithActions(entities.ActionUpdate))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -360,7 +361,7 @@ func (s *store) DeleteIncident(ctx context.Context, id uuid.UUID) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = rbac.Check(ctx, &inc, rbac.WithActions(rbac.ActionDelete))
|
_, err = rbac.Check(ctx, &inc, rbac.WithActions(entities.ActionDelete))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/pb"
|
"dynatron.me/x/stillbox/pkg/pb"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
@ -39,7 +39,7 @@ func New() *Nexus {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nexus) Go(ctx context.Context) {
|
func (n *Nexus) Go(ctx context.Context) {
|
||||||
ctx = rbac.CtxWithSubject(ctx, &rbac.SystemServiceSubject{Name: "nexus"})
|
ctx = entities.CtxWithSubject(ctx, &entities.SystemServiceSubject{Name: "nexus"})
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case call, ok := <-n.callCh:
|
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 (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
|
|
||||||
rbac "dynatron.me/x/stillbox/pkg/rbac"
|
entities "dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
mock "github.com/stretchr/testify/mock"
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
rbac "dynatron.me/x/stillbox/pkg/rbac"
|
||||||
|
|
||||||
restrict "github.com/el-mike/restrict/v2"
|
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
|
// 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))
|
_va := make([]interface{}, len(opts))
|
||||||
for _i := range opts {
|
for _i := range opts {
|
||||||
_va[_i] = opts[_i]
|
_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")
|
panic("no return value specified for Check")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 rbac.Subject
|
var r0 entities.Subject
|
||||||
var r1 error
|
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...)
|
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...)
|
r0 = rf(ctx, res, opts...)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
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
|
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)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
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)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package rbac
|
package policy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -6,7 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||||||
|
|
||||||
"github.com/el-mike/restrict/v2"
|
"github.com/el-mike/restrict/v2"
|
||||||
"github.com/google/uuid"
|
"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"))
|
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 {
|
if err != nil {
|
||||||
return restrict.NewConditionNotSatisfiedError(c, r, err)
|
return restrict.NewConditionNotSatisfiedError(c, r, err)
|
||||||
}
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
package rbac
|
package policy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
|
|
||||||
"github.com/el-mike/restrict/v2"
|
"github.com/el-mike/restrict/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,90 +21,90 @@ const (
|
||||||
|
|
||||||
var Policy = &restrict.PolicyDefinition{
|
var Policy = &restrict.PolicyDefinition{
|
||||||
Roles: restrict.Roles{
|
Roles: restrict.Roles{
|
||||||
RoleUser: {
|
entities.RoleUser: {
|
||||||
Description: "An authenticated user",
|
Description: "An authenticated user",
|
||||||
Grants: restrict.GrantsMap{
|
Grants: restrict.GrantsMap{
|
||||||
ResourceIncident: {
|
entities.ResourceIncident: {
|
||||||
&restrict.Permission{Action: ActionRead},
|
&restrict.Permission{Action: entities.ActionRead},
|
||||||
&restrict.Permission{Action: ActionCreate},
|
&restrict.Permission{Action: entities.ActionCreate},
|
||||||
&restrict.Permission{Preset: PresetUpdateOwn},
|
&restrict.Permission{Preset: PresetUpdateOwn},
|
||||||
&restrict.Permission{Preset: PresetDeleteOwn},
|
&restrict.Permission{Preset: PresetDeleteOwn},
|
||||||
&restrict.Permission{Preset: PresetShareOwn},
|
&restrict.Permission{Preset: PresetShareOwn},
|
||||||
},
|
},
|
||||||
ResourceCall: {
|
entities.ResourceCall: {
|
||||||
&restrict.Permission{Action: ActionRead},
|
&restrict.Permission{Action: entities.ActionRead},
|
||||||
&restrict.Permission{Action: ActionCreate},
|
&restrict.Permission{Action: entities.ActionCreate},
|
||||||
&restrict.Permission{Preset: PresetUpdateSubmitter},
|
&restrict.Permission{Preset: PresetUpdateSubmitter},
|
||||||
&restrict.Permission{Preset: PresetDeleteSubmitter},
|
&restrict.Permission{Preset: PresetDeleteSubmitter},
|
||||||
&restrict.Permission{Action: ActionShare},
|
&restrict.Permission{Action: entities.ActionShare},
|
||||||
},
|
},
|
||||||
ResourceTalkgroup: {
|
entities.ResourceTalkgroup: {
|
||||||
&restrict.Permission{Action: ActionRead},
|
&restrict.Permission{Action: entities.ActionRead},
|
||||||
},
|
},
|
||||||
ResourceShare: {
|
entities.ResourceShare: {
|
||||||
&restrict.Permission{Action: ActionRead},
|
&restrict.Permission{Action: entities.ActionRead},
|
||||||
&restrict.Permission{Action: ActionCreate},
|
&restrict.Permission{Action: entities.ActionCreate},
|
||||||
&restrict.Permission{Preset: PresetUpdateOwn},
|
&restrict.Permission{Preset: PresetUpdateOwn},
|
||||||
&restrict.Permission{Preset: PresetDeleteOwn},
|
&restrict.Permission{Preset: PresetDeleteOwn},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RoleSubmitter: {
|
entities.RoleSubmitter: {
|
||||||
Description: "A role that can submit calls",
|
Description: "A role that can submit calls",
|
||||||
Grants: restrict.GrantsMap{
|
Grants: restrict.GrantsMap{
|
||||||
ResourceCall: {
|
entities.ResourceCall: {
|
||||||
&restrict.Permission{Action: ActionCreate},
|
&restrict.Permission{Action: entities.ActionCreate},
|
||||||
},
|
},
|
||||||
ResourceTalkgroup: {
|
entities.ResourceTalkgroup: {
|
||||||
// for learning TGs
|
// for learning TGs
|
||||||
&restrict.Permission{Action: ActionCreate},
|
&restrict.Permission{Action: entities.ActionCreate},
|
||||||
&restrict.Permission{Action: ActionUpdate},
|
&restrict.Permission{Action: entities.ActionUpdate},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RoleShareGuest: {
|
entities.RoleShareGuest: {
|
||||||
Description: "Someone who has a valid share link",
|
Description: "Someone who has a valid share link",
|
||||||
Grants: restrict.GrantsMap{
|
Grants: restrict.GrantsMap{
|
||||||
ResourceCall: {
|
entities.ResourceCall: {
|
||||||
&restrict.Permission{Preset: PresetReadShared},
|
&restrict.Permission{Preset: PresetReadShared},
|
||||||
&restrict.Permission{Preset: PresetReadInSharedIncident},
|
&restrict.Permission{Preset: PresetReadInSharedIncident},
|
||||||
},
|
},
|
||||||
ResourceIncident: {
|
entities.ResourceIncident: {
|
||||||
&restrict.Permission{Preset: PresetReadShared},
|
&restrict.Permission{Preset: PresetReadShared},
|
||||||
},
|
},
|
||||||
ResourceTalkgroup: {
|
entities.ResourceTalkgroup: {
|
||||||
&restrict.Permission{Action: ActionRead},
|
&restrict.Permission{Action: entities.ActionRead},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RoleAdmin: {
|
entities.RoleAdmin: {
|
||||||
Parents: []string{RoleUser},
|
Parents: []string{entities.RoleUser},
|
||||||
Grants: restrict.GrantsMap{
|
Grants: restrict.GrantsMap{
|
||||||
ResourceIncident: {
|
entities.ResourceIncident: {
|
||||||
&restrict.Permission{Action: ActionUpdate},
|
&restrict.Permission{Action: entities.ActionUpdate},
|
||||||
&restrict.Permission{Action: ActionDelete},
|
&restrict.Permission{Action: entities.ActionDelete},
|
||||||
&restrict.Permission{Action: ActionShare},
|
&restrict.Permission{Action: entities.ActionShare},
|
||||||
},
|
},
|
||||||
ResourceCall: {
|
entities.ResourceCall: {
|
||||||
&restrict.Permission{Action: ActionUpdate},
|
&restrict.Permission{Action: entities.ActionUpdate},
|
||||||
&restrict.Permission{Action: ActionDelete},
|
&restrict.Permission{Action: entities.ActionDelete},
|
||||||
&restrict.Permission{Action: ActionShare},
|
&restrict.Permission{Action: entities.ActionShare},
|
||||||
},
|
},
|
||||||
ResourceTalkgroup: {
|
entities.ResourceTalkgroup: {
|
||||||
&restrict.Permission{Action: ActionUpdate},
|
&restrict.Permission{Action: entities.ActionUpdate},
|
||||||
&restrict.Permission{Action: ActionCreate},
|
&restrict.Permission{Action: entities.ActionCreate},
|
||||||
&restrict.Permission{Action: ActionDelete},
|
&restrict.Permission{Action: entities.ActionDelete},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RoleSystem: {
|
entities.RoleSystem: {
|
||||||
Parents: []string{RoleSystem},
|
Parents: []string{entities.RoleSystem},
|
||||||
},
|
},
|
||||||
RolePublic: {
|
entities.RolePublic: {
|
||||||
/*
|
/*
|
||||||
Grants: restrict.GrantsMap{
|
Grants: restrict.GrantsMap{
|
||||||
ResourceShare: {
|
entities.ResourceShare: {
|
||||||
&restrict.Permission{Action: ActionRead},
|
&restrict.Permission{Action: entities.ActionRead},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
@ -110,7 +112,7 @@ var Policy = &restrict.PolicyDefinition{
|
||||||
},
|
},
|
||||||
PermissionPresets: restrict.PermissionPresets{
|
PermissionPresets: restrict.PermissionPresets{
|
||||||
PresetUpdateOwn: &restrict.Permission{
|
PresetUpdateOwn: &restrict.Permission{
|
||||||
Action: ActionUpdate,
|
Action: entities.ActionUpdate,
|
||||||
Conditions: restrict.Conditions{
|
Conditions: restrict.Conditions{
|
||||||
&restrict.EqualCondition{
|
&restrict.EqualCondition{
|
||||||
ID: "isOwner",
|
ID: "isOwner",
|
||||||
|
@ -126,7 +128,7 @@ var Policy = &restrict.PolicyDefinition{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PresetDeleteOwn: &restrict.Permission{
|
PresetDeleteOwn: &restrict.Permission{
|
||||||
Action: ActionDelete,
|
Action: entities.ActionDelete,
|
||||||
Conditions: restrict.Conditions{
|
Conditions: restrict.Conditions{
|
||||||
&restrict.EqualCondition{
|
&restrict.EqualCondition{
|
||||||
ID: "isOwner",
|
ID: "isOwner",
|
||||||
|
@ -142,7 +144,7 @@ var Policy = &restrict.PolicyDefinition{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PresetShareOwn: &restrict.Permission{
|
PresetShareOwn: &restrict.Permission{
|
||||||
Action: ActionShare,
|
Action: entities.ActionShare,
|
||||||
Conditions: restrict.Conditions{
|
Conditions: restrict.Conditions{
|
||||||
&restrict.EqualCondition{
|
&restrict.EqualCondition{
|
||||||
ID: "isOwner",
|
ID: "isOwner",
|
||||||
|
@ -158,7 +160,7 @@ var Policy = &restrict.PolicyDefinition{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PresetUpdateSubmitter: &restrict.Permission{
|
PresetUpdateSubmitter: &restrict.Permission{
|
||||||
Action: ActionUpdate,
|
Action: entities.ActionUpdate,
|
||||||
Conditions: restrict.Conditions{
|
Conditions: restrict.Conditions{
|
||||||
&SubmitterEqualCondition{
|
&SubmitterEqualCondition{
|
||||||
ID: "isSubmitter",
|
ID: "isSubmitter",
|
||||||
|
@ -174,7 +176,7 @@ var Policy = &restrict.PolicyDefinition{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PresetDeleteSubmitter: &restrict.Permission{
|
PresetDeleteSubmitter: &restrict.Permission{
|
||||||
Action: ActionDelete,
|
Action: entities.ActionDelete,
|
||||||
Conditions: restrict.Conditions{
|
Conditions: restrict.Conditions{
|
||||||
&SubmitterEqualCondition{
|
&SubmitterEqualCondition{
|
||||||
ID: "isSubmitter",
|
ID: "isSubmitter",
|
||||||
|
@ -190,7 +192,7 @@ var Policy = &restrict.PolicyDefinition{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PresetShareSubmitter: &restrict.Permission{
|
PresetShareSubmitter: &restrict.Permission{
|
||||||
Action: ActionShare,
|
Action: entities.ActionShare,
|
||||||
Conditions: restrict.Conditions{
|
Conditions: restrict.Conditions{
|
||||||
&SubmitterEqualCondition{
|
&SubmitterEqualCondition{
|
||||||
ID: "isSubmitter",
|
ID: "isSubmitter",
|
||||||
|
@ -206,7 +208,7 @@ var Policy = &restrict.PolicyDefinition{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PresetReadShared: &restrict.Permission{
|
PresetReadShared: &restrict.Permission{
|
||||||
Action: ActionRead,
|
Action: entities.ActionRead,
|
||||||
Conditions: restrict.Conditions{
|
Conditions: restrict.Conditions{
|
||||||
&restrict.EqualCondition{
|
&restrict.EqualCondition{
|
||||||
ID: "isOwner",
|
ID: "isOwner",
|
||||||
|
@ -222,7 +224,7 @@ var Policy = &restrict.PolicyDefinition{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PresetReadInSharedIncident: &restrict.Permission{
|
PresetReadInSharedIncident: &restrict.Permission{
|
||||||
Action: ActionRead,
|
Action: entities.ActionRead,
|
||||||
Conditions: restrict.Conditions{
|
Conditions: restrict.Conditions{
|
||||||
&CallInIncidentCondition{
|
&CallInIncidentCondition{
|
||||||
ID: "callInIncident",
|
ID: "callInIncident",
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
|
|
||||||
"github.com/el-mike/restrict/v2"
|
"github.com/el-mike/restrict/v2"
|
||||||
"github.com/el-mike/restrict/v2/adapters"
|
"github.com/el-mike/restrict/v2/adapters"
|
||||||
)
|
)
|
||||||
|
@ -12,14 +14,6 @@ var (
|
||||||
ErrBadSubject = errors.New("bad subject in token")
|
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 {
|
func ErrAccessDenied(err error) *restrict.AccessDeniedError {
|
||||||
if accessErr, ok := err.(*restrict.AccessDeniedError); ok {
|
if accessErr, ok := err.(*restrict.AccessDeniedError); ok {
|
||||||
return accessErr
|
return accessErr
|
||||||
|
@ -28,15 +22,6 @@ func ErrAccessDenied(err error) *restrict.AccessDeniedError {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SubjectFrom(ctx context.Context) Subject {
|
|
||||||
sub, ok := ctx.Value(SubjectCtxKey).(Subject)
|
|
||||||
if ok {
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(PublicSubject)
|
|
||||||
}
|
|
||||||
|
|
||||||
type rbacCtxKey string
|
type rbacCtxKey string
|
||||||
|
|
||||||
const RBACCtxKey rbacCtxKey = "rbac"
|
const RBACCtxKey rbacCtxKey = "rbac"
|
||||||
|
@ -81,17 +66,8 @@ func UseResource(rsc string) restrict.Resource {
|
||||||
return restrict.UseResource(rsc)
|
return restrict.UseResource(rsc)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Subject interface {
|
|
||||||
restrict.Subject
|
|
||||||
GetName() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Resource interface {
|
|
||||||
restrict.Resource
|
|
||||||
}
|
|
||||||
|
|
||||||
type RBAC interface {
|
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 {
|
type rbac struct {
|
||||||
|
@ -99,8 +75,8 @@ type rbac struct {
|
||||||
access *restrict.AccessManager
|
access *restrict.AccessManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() (*rbac, error) {
|
func New(pol *restrict.PolicyDefinition) (*rbac, error) {
|
||||||
adapter := adapters.NewInMemoryAdapter(Policy)
|
adapter := adapters.NewInMemoryAdapter(pol)
|
||||||
polMan, err := restrict.NewPolicyManager(adapter, true)
|
polMan, err := restrict.NewPolicyManager(adapter, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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...)
|
return FromCtx(ctx).Check(ctx, res, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rbac) Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (Subject, error) {
|
func (r *rbac) Check(ctx context.Context, res restrict.Resource, opts ...CheckOption) (entities.Subject, error) {
|
||||||
sub := SubjectFrom(ctx)
|
sub := entities.SubjectFrom(ctx)
|
||||||
o := checkOptions{}
|
o := checkOptions{}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
|
@ -139,34 +115,5 @@ func (r *rbac) Check(ctx context.Context, res restrict.Resource, opts ...CheckOp
|
||||||
Context: o.context,
|
Context: o.context,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := r.access.Authorize(req)
|
return sub, 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}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/incidents"
|
"dynatron.me/x/stillbox/pkg/incidents"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"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/talkgroups"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
"github.com/el-mike/restrict/v2"
|
"github.com/el-mike/restrict/v2"
|
||||||
|
@ -20,8 +22,8 @@ import (
|
||||||
func TestRBAC(t *testing.T) {
|
func TestRBAC(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
subject rbac.Subject
|
subject entities.Subject
|
||||||
resource rbac.Resource
|
resource entities.Resource
|
||||||
action string
|
action string
|
||||||
expectErr error
|
expectErr error
|
||||||
}{
|
}{
|
||||||
|
@ -32,7 +34,7 @@ func TestRBAC(t *testing.T) {
|
||||||
IsAdmin: true,
|
IsAdmin: true,
|
||||||
},
|
},
|
||||||
resource: &talkgroups.Talkgroup{},
|
resource: &talkgroups.Talkgroup{},
|
||||||
action: rbac.ActionUpdate,
|
action: entities.ActionUpdate,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -45,7 +47,7 @@ func TestRBAC(t *testing.T) {
|
||||||
Name: "test incident",
|
Name: "test incident",
|
||||||
Owner: 4,
|
Owner: 4,
|
||||||
},
|
},
|
||||||
action: rbac.ActionUpdate,
|
action: entities.ActionUpdate,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -57,7 +59,7 @@ func TestRBAC(t *testing.T) {
|
||||||
Name: "test incident",
|
Name: "test incident",
|
||||||
Owner: 4,
|
Owner: 4,
|
||||||
},
|
},
|
||||||
action: rbac.ActionUpdate,
|
action: entities.ActionUpdate,
|
||||||
expectErr: errors.New(`access denied for Action: "update" on Resource: "Incident"`),
|
expectErr: errors.New(`access denied for Action: "update" on Resource: "Incident"`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -69,7 +71,7 @@ func TestRBAC(t *testing.T) {
|
||||||
Name: "test incident",
|
Name: "test incident",
|
||||||
Owner: 2,
|
Owner: 2,
|
||||||
},
|
},
|
||||||
action: rbac.ActionUpdate,
|
action: entities.ActionUpdate,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -81,7 +83,7 @@ func TestRBAC(t *testing.T) {
|
||||||
Name: "test incident",
|
Name: "test incident",
|
||||||
Owner: 6,
|
Owner: 6,
|
||||||
},
|
},
|
||||||
action: rbac.ActionDelete,
|
action: entities.ActionDelete,
|
||||||
expectErr: errors.New(`access denied for Action: "delete" on Resource: "Incident"`),
|
expectErr: errors.New(`access denied for Action: "delete" on Resource: "Incident"`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -93,7 +95,7 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &calls.Call{
|
resource: &calls.Call{
|
||||||
Submitter: common.PtrTo(users.UserID(4)),
|
Submitter: common.PtrTo(users.UserID(4)),
|
||||||
},
|
},
|
||||||
action: rbac.ActionUpdate,
|
action: entities.ActionUpdate,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -104,7 +106,7 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &calls.Call{
|
resource: &calls.Call{
|
||||||
Submitter: common.PtrTo(users.UserID(4)),
|
Submitter: common.PtrTo(users.UserID(4)),
|
||||||
},
|
},
|
||||||
action: rbac.ActionUpdate,
|
action: entities.ActionUpdate,
|
||||||
expectErr: errors.New(`access denied for Action: "update" on Resource: "Call"`),
|
expectErr: errors.New(`access denied for Action: "update" on Resource: "Call"`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -115,7 +117,7 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &calls.Call{
|
resource: &calls.Call{
|
||||||
Submitter: common.PtrTo(users.UserID(2)),
|
Submitter: common.PtrTo(users.UserID(2)),
|
||||||
},
|
},
|
||||||
action: rbac.ActionUpdate,
|
action: entities.ActionUpdate,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -126,7 +128,7 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &calls.Call{
|
resource: &calls.Call{
|
||||||
Submitter: nil,
|
Submitter: nil,
|
||||||
},
|
},
|
||||||
action: rbac.ActionUpdate,
|
action: entities.ActionUpdate,
|
||||||
expectErr: errors.New(`access denied for Action: "update" on Resource: "Call"`),
|
expectErr: errors.New(`access denied for Action: "update" on Resource: "Call"`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -137,7 +139,7 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &calls.Call{
|
resource: &calls.Call{
|
||||||
Submitter: common.PtrTo(users.UserID(6)),
|
Submitter: common.PtrTo(users.UserID(6)),
|
||||||
},
|
},
|
||||||
action: rbac.ActionDelete,
|
action: entities.ActionDelete,
|
||||||
expectErr: errors.New(`access denied for Action: "delete" on Resource: "Call"`),
|
expectErr: errors.New(`access denied for Action: "delete" on Resource: "Call"`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -148,7 +150,7 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &calls.Call{
|
resource: &calls.Call{
|
||||||
Submitter: common.PtrTo(users.UserID(6)),
|
Submitter: common.PtrTo(users.UserID(6)),
|
||||||
},
|
},
|
||||||
action: rbac.ActionShare,
|
action: entities.ActionShare,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -160,7 +162,7 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &calls.Call{
|
resource: &calls.Call{
|
||||||
Submitter: common.PtrTo(users.UserID(6)),
|
Submitter: common.PtrTo(users.UserID(6)),
|
||||||
},
|
},
|
||||||
action: rbac.ActionShare,
|
action: entities.ActionShare,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -171,7 +173,7 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &calls.Call{
|
resource: &calls.Call{
|
||||||
Submitter: common.PtrTo(users.UserID(6)),
|
Submitter: common.PtrTo(users.UserID(6)),
|
||||||
},
|
},
|
||||||
action: rbac.ActionShare,
|
action: entities.ActionShare,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -182,7 +184,7 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &incidents.Incident{
|
resource: &incidents.Incident{
|
||||||
Owner: users.UserID(6),
|
Owner: users.UserID(6),
|
||||||
},
|
},
|
||||||
action: rbac.ActionShare,
|
action: entities.ActionShare,
|
||||||
expectErr: errors.New(`access denied for Action: "share" on Resource: "Incident"`),
|
expectErr: errors.New(`access denied for Action: "share" on Resource: "Incident"`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -194,7 +196,7 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &incidents.Incident{
|
resource: &incidents.Incident{
|
||||||
Owner: users.UserID(6),
|
Owner: users.UserID(6),
|
||||||
},
|
},
|
||||||
action: rbac.ActionShare,
|
action: entities.ActionShare,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -205,15 +207,15 @@ func TestRBAC(t *testing.T) {
|
||||||
resource: &incidents.Incident{
|
resource: &incidents.Incident{
|
||||||
Owner: users.UserID(6),
|
Owner: users.UserID(6),
|
||||||
},
|
},
|
||||||
action: rbac.ActionShare,
|
action: entities.ActionShare,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
ctx := rbac.CtxWithSubject(context.Background(), tc.subject)
|
ctx := entities.CtxWithSubject(context.Background(), tc.subject)
|
||||||
rb, err := rbac.New()
|
rb, err := rbac.New(policy.Policy)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
sub, err := rb.Check(ctx, tc.resource, rbac.WithActions(tc.action))
|
sub, err := rb.Check(ctx, tc.resource, rbac.WithActions(tc.action))
|
||||||
if tc.expectErr != nil {
|
if tc.expectErr != nil {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/forms"
|
"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"
|
"dynatron.me/x/stillbox/pkg/shares"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
@ -133,7 +133,7 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = rbac.CtxWithSubject(ctx, sh)
|
ctx = entities.CtxWithSubject(ctx, sh)
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
if params.SubType != nil {
|
if params.SubType != nil {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/nexus"
|
"dynatron.me/x/stillbox/pkg/nexus"
|
||||||
"dynatron.me/x/stillbox/pkg/notify"
|
"dynatron.me/x/stillbox/pkg/notify"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac/policy"
|
||||||
"dynatron.me/x/stillbox/pkg/rest"
|
"dynatron.me/x/stillbox/pkg/rest"
|
||||||
"dynatron.me/x/stillbox/pkg/shares"
|
"dynatron.me/x/stillbox/pkg/shares"
|
||||||
"dynatron.me/x/stillbox/pkg/sinks"
|
"dynatron.me/x/stillbox/pkg/sinks"
|
||||||
|
@ -80,7 +81,7 @@ func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
||||||
tgCache := tgstore.NewCache(db)
|
tgCache := tgstore.NewCache(db)
|
||||||
api := rest.New(cfg.BaseURL.URL())
|
api := rest.New(cfg.BaseURL.URL())
|
||||||
|
|
||||||
rbacSvc, err := rbac.New()
|
rbacSvc, err := rbac.New(policy.Policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ type service struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) Go(ctx context.Context) {
|
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)
|
tick := time.NewTicker(PruneInterval)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/calls/callstore"
|
"dynatron.me/x/stillbox/pkg/calls/callstore"
|
||||||
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -57,11 +58,11 @@ func (s *Share) GetName() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Share) GetRoles() []string {
|
func (s *Share) GetRoles() []string {
|
||||||
return []string{rbac.RoleShareGuest}
|
return []string{entities.RoleShareGuest}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Share) GetResourceName() string {
|
func (s *Share) GetResourceName() string {
|
||||||
return rbac.ResourceShare
|
return entities.ResourceShare
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateShareParams struct {
|
type CreateShareParams struct {
|
||||||
|
@ -88,7 +89,7 @@ func (s *service) checkEntity(ctx context.Context, sh *CreateShareParams) (*time
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = rbac.Check(ctx, &i, rbac.WithActions(rbac.ActionShare))
|
_, err = rbac.Check(ctx, &i, rbac.WithActions(entities.ActionShare))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
"github.com/jackc/pgx/v5"
|
"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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/auth"
|
"dynatron.me/x/stillbox/pkg/auth"
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/rs/zerolog/log"
|
"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)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = h.ing.Ingest(rbac.CtxWithSubject(ctx, submitterSub), call)
|
err = h.ing.Ingest(entities.CtxWithSubject(ctx, submitterSub), call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if rbac.ErrAccessDenied(err) != nil {
|
if rbac.ErrAccessDenied(err) != nil {
|
||||||
log.Error().Err(err).Msg("ingest failed")
|
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
|
if f.hasTags() { // don't bother with DB if no tags
|
||||||
db := database.FromCtx(ctx)
|
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)
|
tagTGs, err := db.GetTalkgroupIDsByTags(ctx, f.TalkgroupTagsAny, f.TalkgroupTagsAll, f.TalkgroupTagsNot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Talkgroup struct {
|
type Talkgroup struct {
|
||||||
|
@ -19,7 +19,7 @@ type Talkgroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Talkgroup) GetResourceName() string {
|
func (t *Talkgroup) GetResourceName() string {
|
||||||
return rbac.ResourceTalkgroup
|
return entities.ResourceTalkgroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Talkgroup) String() string {
|
func (t Talkgroup) String() string {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/config"
|
"dynatron.me/x/stillbox/pkg/config"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
tgsp "dynatron.me/x/stillbox/pkg/talkgroups"
|
tgsp "dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
"dynatron.me/x/stillbox/pkg/users"
|
"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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return "", false
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserID int
|
type UserID int
|
||||||
|
@ -30,11 +31,11 @@ func (u UserID) IsValid() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func From(ctx context.Context) (*User, error) {
|
func From(ctx context.Context) (*User, error) {
|
||||||
sub := rbac.SubjectFrom(ctx)
|
sub := entities.SubjectFrom(ctx)
|
||||||
return FromSubject(sub)
|
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, "+")
|
acts := strings.Split(actions, "+")
|
||||||
subj, err := rbac.FromCtx(ctx).Check(ctx, rsc, rbac.WithActions(acts...))
|
subj, err := rbac.FromCtx(ctx).Check(ctx, rsc, rbac.WithActions(acts...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -44,7 +45,7 @@ func UserCheck(ctx context.Context, rsc rbac.Resource, actions string) (*User, e
|
||||||
return FromSubject(subj)
|
return FromSubject(subj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromSubject(sub rbac.Subject) (*User, error) {
|
func FromSubject(sub entities.Subject) (*User, error) {
|
||||||
if sub == nil {
|
if sub == nil {
|
||||||
return nil, rbac.ErrBadSubject
|
return nil, rbac.ErrBadSubject
|
||||||
}
|
}
|
||||||
|
@ -73,10 +74,10 @@ func (u *User) GetName() string {
|
||||||
func (u *User) GetRoles() []string {
|
func (u *User) GetRoles() []string {
|
||||||
r := make([]string, 1, 2)
|
r := make([]string, 1, 2)
|
||||||
|
|
||||||
r[0] = rbac.RoleUser
|
r[0] = entities.RoleUser
|
||||||
|
|
||||||
if u.IsAdmin {
|
if u.IsAdmin {
|
||||||
r = append(r, rbac.RoleAdmin)
|
r = append(r, entities.RoleAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
|
@ -24,3 +24,11 @@ DELETE FROM shares WHERE id = @id;
|
||||||
|
|
||||||
-- name: PruneShares :exec
|
-- name: PruneShares :exec
|
||||||
DELETE FROM shares WHERE expiration < NOW();
|
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