diff --git a/pkg/alerting/alerting.go b/pkg/alerting/alerting.go index e4bf912..769781b 100644 --- a/pkg/alerting/alerting.go +++ b/pkg/alerting/alerting.go @@ -185,7 +185,7 @@ func (as *alerter) eval(ctx context.Context, now time.Time, testMode bool) ([]al continue } - if!tgr.Talkgroup.Alert { + if !tgr.Talkgroup.Alert { continue } @@ -395,4 +395,4 @@ func (*noopAlerter) SinkType() string { return "noopA func (*noopAlerter) Call(_ context.Context, _ *calls.Call) error { return nil } func (*noopAlerter) Go(_ context.Context) {} func (*noopAlerter) Enabled() bool { return false } -func (*noopAlerter) HUP(_ *config.Config) { } +func (*noopAlerter) HUP(_ *config.Config) {} diff --git a/pkg/calls/callstore/store.go b/pkg/calls/callstore/store.go index 85aed7a..633b166 100644 --- a/pkg/calls/callstore/store.go +++ b/pkg/calls/callstore/store.go @@ -145,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(entities.ResourceCall), rbac.WithActions(entities.ActionRead)) + _, err := rbac.Check(ctx, &calls.Call{ID: id}, rbac.WithActions(entities.ActionRead)) if err != nil { return nil, err } diff --git a/pkg/rbac/policy/policy.go b/pkg/rbac/policy/policy.go index d2721e4..16addcc 100644 --- a/pkg/rbac/policy/policy.go +++ b/pkg/rbac/policy/policy.go @@ -79,7 +79,7 @@ var Policy = &restrict.PolicyDefinition{ }, entities.RoleAdmin: { Description: "A superuser", - Parents: []string{entities.RoleUser}, + Parents: []string{entities.RoleUser}, Grants: restrict.GrantsMap{ entities.ResourceIncident: { &restrict.Permission{Action: entities.ActionRead}, @@ -109,7 +109,7 @@ var Policy = &restrict.PolicyDefinition{ }, entities.RoleSystem: { Description: "A system service", - Parents: []string{entities.RoleAdmin}, + Parents: []string{entities.RoleAdmin}, }, entities.RolePublic: { /* diff --git a/pkg/rest/api.go b/pkg/rest/api.go index 8f0928c..0f249b6 100644 --- a/pkg/rest/api.go +++ b/pkg/rest/api.go @@ -51,8 +51,9 @@ func New(baseURL url.URL) *api { s.shares = newShareAPI(&baseURL, ShareHandlers{ ShareRequestCall: s.calls.shareCallRoute, + ShareRequestCallInfo: respondShareHandler(s.calls.getCallInfo), ShareRequestCallDL: s.calls.shareCallDLRoute, - ShareRequestIncident: s.incidents.getIncident, + ShareRequestIncident: respondShareHandler(s.incidents.getIncident), ShareRequestIncidentM3U: s.incidents.getCallsM3U, ShareRequestTalkgroups: s.tgs.getTGsShareRoute, }, diff --git a/pkg/rest/calls.go b/pkg/rest/calls.go index 1492f6c..cdbc5bf 100644 --- a/pkg/rest/calls.go +++ b/pkg/rest/calls.go @@ -1,6 +1,7 @@ package rest import ( + "context" "errors" "fmt" "mime" @@ -11,7 +12,6 @@ import ( "dynatron.me/x/stillbox/internal/forms" "dynatron.me/x/stillbox/pkg/calls/callstore" "dynatron.me/x/stillbox/pkg/database" - "dynatron.me/x/stillbox/pkg/shares" "github.com/go-chi/chi/v5" "github.com/google/uuid" @@ -102,7 +102,12 @@ func (ca *callsAPI) getAudio(p getAudioParams, w http.ResponseWriter, r *http.Re _, _ = w.Write(call.AudioBlob) } -func (ca *callsAPI) shareCallRoute(id ID, _ *shares.Share, w http.ResponseWriter, r *http.Request) { +func (ca *callsAPI) getCallInfo(ctx context.Context, id ID) (SharedItem, error) { + cs := callstore.FromCtx(ctx) + return cs.Call(ctx, id.(uuid.UUID)) +} + +func (ca *callsAPI) shareCallRoute(id ID, w http.ResponseWriter, r *http.Request) { p := getAudioParams{ CallID: common.PtrTo(id.(uuid.UUID)), } @@ -110,7 +115,7 @@ func (ca *callsAPI) shareCallRoute(id ID, _ *shares.Share, w http.ResponseWriter ca.getAudio(p, w, r) } -func (ca *callsAPI) shareCallDLRoute(id ID, _ *shares.Share, w http.ResponseWriter, r *http.Request) { +func (ca *callsAPI) shareCallDLRoute(id ID, w http.ResponseWriter, r *http.Request) { p := getAudioParams{ CallID: common.PtrTo(id.(uuid.UUID)), Download: common.PtrTo("download"), diff --git a/pkg/rest/incidents.go b/pkg/rest/incidents.go index ff850b0..ef52255 100644 --- a/pkg/rest/incidents.go +++ b/pkg/rest/incidents.go @@ -2,6 +2,7 @@ package rest import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -12,7 +13,6 @@ import ( "dynatron.me/x/stillbox/internal/jsontypes" "dynatron.me/x/stillbox/pkg/incidents" "dynatron.me/x/stillbox/pkg/incidents/incstore" - "dynatron.me/x/stillbox/pkg/shares" "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "github.com/go-chi/chi/v5" @@ -91,22 +91,22 @@ func (ia *incidentsAPI) createIncident(w http.ResponseWriter, r *http.Request) { func (ia *incidentsAPI) getIncidentRoute(w http.ResponseWriter, r *http.Request) { id, err := idOnlyParam(w, r) if err != nil { + wErr(w, r, autoError(err)) return } - ia.getIncident(id, nil, w, r) -} - -func (ia *incidentsAPI) getIncident(id ID, share *shares.Share, w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - incs := incstore.FromCtx(ctx) - inc, err := incs.Incident(ctx, id.(uuid.UUID)) + e, err := ia.getIncident(r.Context(), id) if err != nil { wErr(w, r, autoError(err)) return } - respond(w, r, inc) + respond(w, r, e) +} + +func (ia *incidentsAPI) getIncident(ctx context.Context, id ID) (SharedItem, error) { + incs := incstore.FromCtx(ctx) + return incs.Incident(ctx, id.(uuid.UUID)) } func (ia *incidentsAPI) updateIncident(w http.ResponseWriter, r *http.Request) { @@ -195,10 +195,10 @@ func (ia *incidentsAPI) getCallsM3URoute(w http.ResponseWriter, r *http.Request) return } - ia.getCallsM3U(id, nil, w, r) + ia.getCallsM3U(id, w, r) } -func (ia *incidentsAPI) getCallsM3U(id ID, share *shares.Share, w http.ResponseWriter, r *http.Request) { +func (ia *incidentsAPI) getCallsM3U(id ID, w http.ResponseWriter, r *http.Request) { ctx := r.Context() incs := incstore.FromCtx(ctx) tgst := tgstore.FromCtx(ctx) @@ -214,6 +214,7 @@ func (ia *incidentsAPI) getCallsM3U(id ID, share *shares.Share, w http.ResponseW callUrl := common.PtrTo(*ia.baseURL) urlRoot := "/api/call" filename := inc.PlaylistFilename() + share := ShareFrom(ctx) if share != nil { urlRoot = fmt.Sprintf("/share/%s/call/", share.ID) filename += "_" + share.ID diff --git a/pkg/rest/share.go b/pkg/rest/share.go index ebbd63f..44af365 100644 --- a/pkg/rest/share.go +++ b/pkg/rest/share.go @@ -1,6 +1,7 @@ package rest import ( + "context" "errors" "net/http" "net/url" @@ -23,6 +24,7 @@ type ShareRequestType string const ( ShareRequestCall ShareRequestType = "call" + ShareRequestCallInfo ShareRequestType = "callinfo" ShareRequestCallDL ShareRequestType = "callDL" ShareRequestIncident ShareRequestType = "incident" ShareRequestIncidentM3U ShareRequestType = "m3u" @@ -31,7 +33,7 @@ const ( func (rt ShareRequestType) IsValid() bool { switch rt { - case ShareRequestCall, ShareRequestCallDL, ShareRequestIncident, + case ShareRequestCall, ShareRequestCallInfo, ShareRequestCallDL, ShareRequestIncident, ShareRequestIncidentM3U, ShareRequestTalkgroups: return true } @@ -39,25 +41,59 @@ func (rt ShareRequestType) IsValid() bool { return false } -func (rt ShareRequestType) IsValidSubtype() bool { - switch rt { - case ShareRequestCall, ShareRequestCallDL, ShareRequestTalkgroups: - return true - } - - return false -} - type ID interface { } -type HandlerFunc func(id ID, share *shares.Share, w http.ResponseWriter, r *http.Request) -type ShareHandlers map[ShareRequestType]HandlerFunc +type ShareHandlerFunc func(id ID, w http.ResponseWriter, r *http.Request) +type ShareHandlers map[ShareRequestType]ShareHandlerFunc type shareAPI struct { baseURL *url.URL shnd ShareHandlers } +type EntityFunc func(ctx context.Context, id ID) (SharedItem, error) +type SharedItem interface { +} + +type shareResponse struct { + ID ID `json:"id"` + Type shares.EntityType `json:"type"` + Item SharedItem `json:"item,omitempty"` +} + +func ShareFrom(ctx context.Context) *shares.Share { + if share, hasShare := entities.SubjectFrom(ctx).(*shares.Share); hasShare { + return share + } + + return nil +} + +func respondShareHandler(ie EntityFunc) ShareHandlerFunc { + return func(id ID, w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + share := ShareFrom(ctx) + if share == nil { + wErr(w, r, autoError(ErrBadShare)) + return + } + + res, err := ie(r.Context(), id) + if err != nil { + wErr(w, r, autoError(err)) + return + } + + sRes := shareResponse{ + ID: id, + Type: share.Type, + Item: res, + } + + respond(w, r, sRes) + } +} + func newShareAPI(baseURL *url.URL, shnd ShareHandlers) *shareAPI { return &shareAPI{ baseURL: baseURL, @@ -135,12 +171,11 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) { } else { switch sh.Type { case shares.EntityCall: - rType = ShareRequestCall + rType = ShareRequestCallInfo params.SubID = common.PtrTo(sh.EntityID.String()) case shares.EntityIncident: rType = ShareRequestIncident } - w.Header().Set("X-Share-Type", string(rType)) } if !rType.IsValid() { @@ -158,21 +193,22 @@ func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) { switch rType { case ShareRequestTalkgroups: - sa.shnd[rType](nil, sh, w, r) - case ShareRequestCall, ShareRequestCallDL: - if params.SubID == nil { - wErr(w, r, autoError(ErrBadShare)) - return + sa.shnd[rType](nil, w, r) + case ShareRequestCall, ShareRequestCallInfo, ShareRequestCallDL: + var subIDU uuid.UUID + if params.SubID != nil { + subIDU, err = uuid.Parse(*params.SubID) + if err != nil { + wErr(w, r, badRequest(err)) + return + } + } else { + subIDU = sh.EntityID } - subIDU, err := uuid.Parse(*params.SubID) - if err != nil { - wErr(w, r, badRequest(err)) - return - } - sa.shnd[rType](subIDU, sh, w, r) + sa.shnd[rType](subIDU, w, r) case ShareRequestIncident, ShareRequestIncidentM3U: - sa.shnd[rType](sh.EntityID, sh, w, r) + sa.shnd[rType](sh.EntityID, w, r) } } diff --git a/pkg/rest/talkgroups.go b/pkg/rest/talkgroups.go index d97e9ea..8e251d8 100644 --- a/pkg/rest/talkgroups.go +++ b/pkg/rest/talkgroups.go @@ -8,7 +8,6 @@ import ( "dynatron.me/x/stillbox/internal/forms" "dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/incidents/incstore" - "dynatron.me/x/stillbox/pkg/shares" "dynatron.me/x/stillbox/pkg/talkgroups" "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "dynatron.me/x/stillbox/pkg/talkgroups/xport" @@ -161,10 +160,16 @@ func (tga *talkgroupAPI) postPaginated(w http.ResponseWriter, r *http.Request) { respond(w, r, res) } -func (tga *talkgroupAPI) getTGsShareRoute(_ ID, share *shares.Share, w http.ResponseWriter, r *http.Request) { +func (tga *talkgroupAPI) getTGsShareRoute(_ ID, w http.ResponseWriter, r *http.Request) { ctx := r.Context() tgs := tgstore.FromCtx(ctx) + share := ShareFrom(ctx) + if share == nil { + wErr(w, r, autoError(ErrBadShare)) + return + } + tgIDs, err := incstore.FromCtx(ctx).TGsIn(ctx, share.EntityID) if err != nil { wErr(w, r, autoError(err))