WIP
This commit is contained in:
parent
a9f64f74fb
commit
b171e8431a
12 changed files with 426 additions and 397 deletions
|
@ -123,7 +123,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, rbac.UseResource(rbac.ResourceCall), rbac.WithActions(rbac.ActionRead))
|
_, err := rbac.Check(ctx, &calls.Call{ID: id}, rbac.WithActions(rbac.ActionRead))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ type Configuration struct {
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
BaseURL jsontypes.URL `yaml:"baseURL"`
|
BaseURL jsontypes.URL `yaml:"baseURL"`
|
||||||
|
DumpRoutes bool `yaml:"dumpRoutes"`
|
||||||
DB DB `yaml:"db"`
|
DB DB `yaml:"db"`
|
||||||
CORS CORS `yaml:"cors"`
|
CORS CORS `yaml:"cors"`
|
||||||
Auth Auth `yaml:"auth"`
|
Auth Auth `yaml:"auth"`
|
||||||
|
|
|
@ -278,7 +278,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, new(incidents.Incident), rbac.WithActions(rbac.ActionRead))
|
_, err := rbac.Check(ctx, &incidents.Incident{ID: id}, rbac.WithActions(rbac.ActionRead))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
94
pkg/rbac/conditions.go
Normal file
94
pkg/rbac/conditions.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package rbac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/el-mike/restrict/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SubmitterEqualConditionType = "SUBMITTER_EQUAL"
|
||||||
|
InMapConditionType = "IN_MAP"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SubmitterEqualCondition struct {
|
||||||
|
ID string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
|
Left *restrict.ValueDescriptor `json:"left" yaml:"left"`
|
||||||
|
Right *restrict.ValueDescriptor `json:"right" yaml:"right"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubmitterEqualCondition) Type() string {
|
||||||
|
return SubmitterEqualConditionType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SubmitterEqualCondition) Check(r *restrict.AccessRequest) error {
|
||||||
|
left, err := c.Left.GetValue(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
right, err := c.Right.GetValue(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lVal := reflect.ValueOf(left)
|
||||||
|
rVal := reflect.ValueOf(right)
|
||||||
|
|
||||||
|
// deref Left. this is the difference between us and EqualCondition
|
||||||
|
for lVal.Kind() == reflect.Pointer {
|
||||||
|
lVal = lVal.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lVal.IsValid() || !reflect.DeepEqual(rVal.Interface(), lVal.Interface()) {
|
||||||
|
return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf("values \"%v\" and \"%v\" are not equal", left, right))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubmitterEqualConditionFactory() restrict.Condition {
|
||||||
|
return new(SubmitterEqualCondition)
|
||||||
|
}
|
||||||
|
|
||||||
|
type InMapCondition[K comparable, V any] struct {
|
||||||
|
ID string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
|
Key *restrict.ValueDescriptor `json:"key" yaml:"key"`
|
||||||
|
Map *restrict.ValueDescriptor `json:"map" yaml:"map"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMapCondition[K, V]) Type() string {
|
||||||
|
return SubmitterEqualConditionType
|
||||||
|
}
|
||||||
|
|
||||||
|
func InMapConditionFactory[K comparable, V any]() restrict.Condition {
|
||||||
|
return new(InMapCondition[K, V])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InMapCondition[K, V]) Check(r *restrict.AccessRequest) error {
|
||||||
|
cKey, err := c.Key.GetValue(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cMap, err := c.Map.GetValue(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyVal := reflect.ValueOf(cKey)
|
||||||
|
mapVal := reflect.ValueOf(cMap)
|
||||||
|
|
||||||
|
key := keyVal.Interface().(K)
|
||||||
|
|
||||||
|
if _, in := mapVal.Interface().(map[K]V)[key]; !in {
|
||||||
|
return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf("key '%v' not in map", key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
restrict.RegisterConditionFactory(SubmitterEqualConditionType, SubmitterEqualConditionFactory)
|
||||||
|
}
|
211
pkg/rbac/policy.go
Normal file
211
pkg/rbac/policy.go
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
package rbac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/el-mike/restrict/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var policy = &restrict.PolicyDefinition{
|
||||||
|
Roles: restrict.Roles{
|
||||||
|
RoleUser: {
|
||||||
|
Description: "An authenticated user",
|
||||||
|
Grants: restrict.GrantsMap{
|
||||||
|
ResourceIncident: {
|
||||||
|
&restrict.Permission{Action: ActionRead},
|
||||||
|
&restrict.Permission{Action: ActionCreate},
|
||||||
|
&restrict.Permission{Preset: PresetUpdateOwn},
|
||||||
|
&restrict.Permission{Preset: PresetDeleteOwn},
|
||||||
|
&restrict.Permission{Preset: PresetShareOwn},
|
||||||
|
},
|
||||||
|
ResourceCall: {
|
||||||
|
&restrict.Permission{Action: ActionRead},
|
||||||
|
&restrict.Permission{Action: ActionCreate},
|
||||||
|
&restrict.Permission{Preset: PresetUpdateSubmitter},
|
||||||
|
&restrict.Permission{Preset: PresetDeleteSubmitter},
|
||||||
|
&restrict.Permission{Action: ActionShare},
|
||||||
|
},
|
||||||
|
ResourceTalkgroup: {
|
||||||
|
&restrict.Permission{Action: ActionRead},
|
||||||
|
},
|
||||||
|
ResourceShare: {
|
||||||
|
&restrict.Permission{Action: ActionRead},
|
||||||
|
&restrict.Permission{Action: ActionCreate},
|
||||||
|
&restrict.Permission{Preset: PresetUpdateOwn},
|
||||||
|
&restrict.Permission{Preset: PresetDeleteOwn},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RoleSubmitter: {
|
||||||
|
Description: "A role that can submit calls",
|
||||||
|
Grants: restrict.GrantsMap{
|
||||||
|
ResourceCall: {
|
||||||
|
&restrict.Permission{Action: ActionCreate},
|
||||||
|
},
|
||||||
|
ResourceTalkgroup: {
|
||||||
|
// for learning TGs
|
||||||
|
&restrict.Permission{Action: ActionCreate},
|
||||||
|
&restrict.Permission{Action: ActionUpdate},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RoleShareGuest: {
|
||||||
|
Description: "Someone who has a valid share link",
|
||||||
|
Grants: restrict.GrantsMap{
|
||||||
|
ResourceCall: {
|
||||||
|
&restrict.Permission{Preset: PresetReadShared},
|
||||||
|
},
|
||||||
|
ResourceIncident: {
|
||||||
|
&restrict.Permission{Preset: PresetReadShared},
|
||||||
|
},
|
||||||
|
ResourceTalkgroup: {
|
||||||
|
&restrict.Permission{Action: ActionRead},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RoleAdmin: {
|
||||||
|
Parents: []string{RoleUser},
|
||||||
|
Grants: restrict.GrantsMap{
|
||||||
|
ResourceIncident: {
|
||||||
|
&restrict.Permission{Action: ActionUpdate},
|
||||||
|
&restrict.Permission{Action: ActionDelete},
|
||||||
|
&restrict.Permission{Action: ActionShare},
|
||||||
|
},
|
||||||
|
ResourceCall: {
|
||||||
|
&restrict.Permission{Action: ActionUpdate},
|
||||||
|
&restrict.Permission{Action: ActionDelete},
|
||||||
|
&restrict.Permission{Action: ActionShare},
|
||||||
|
},
|
||||||
|
ResourceTalkgroup: {
|
||||||
|
&restrict.Permission{Action: ActionUpdate},
|
||||||
|
&restrict.Permission{Action: ActionCreate},
|
||||||
|
&restrict.Permission{Action: ActionDelete},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RoleSystem: {
|
||||||
|
Parents: []string{RoleSystem},
|
||||||
|
},
|
||||||
|
RolePublic: {
|
||||||
|
/*
|
||||||
|
Grants: restrict.GrantsMap{
|
||||||
|
ResourceShare: {
|
||||||
|
&restrict.Permission{Action: ActionRead},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PermissionPresets: restrict.PermissionPresets{
|
||||||
|
PresetUpdateOwn: &restrict.Permission{
|
||||||
|
Action: ActionUpdate,
|
||||||
|
Conditions: restrict.Conditions{
|
||||||
|
&restrict.EqualCondition{
|
||||||
|
ID: "isOwner",
|
||||||
|
Left: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.ResourceField,
|
||||||
|
Field: "Owner",
|
||||||
|
},
|
||||||
|
Right: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.SubjectField,
|
||||||
|
Field: "ID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PresetDeleteOwn: &restrict.Permission{
|
||||||
|
Action: ActionDelete,
|
||||||
|
Conditions: restrict.Conditions{
|
||||||
|
&restrict.EqualCondition{
|
||||||
|
ID: "isOwner",
|
||||||
|
Left: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.ResourceField,
|
||||||
|
Field: "Owner",
|
||||||
|
},
|
||||||
|
Right: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.SubjectField,
|
||||||
|
Field: "ID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PresetShareOwn: &restrict.Permission{
|
||||||
|
Action: ActionShare,
|
||||||
|
Conditions: restrict.Conditions{
|
||||||
|
&restrict.EqualCondition{
|
||||||
|
ID: "isOwner",
|
||||||
|
Left: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.ResourceField,
|
||||||
|
Field: "Owner",
|
||||||
|
},
|
||||||
|
Right: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.SubjectField,
|
||||||
|
Field: "ID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PresetUpdateSubmitter: &restrict.Permission{
|
||||||
|
Action: ActionUpdate,
|
||||||
|
Conditions: restrict.Conditions{
|
||||||
|
&SubmitterEqualCondition{
|
||||||
|
ID: "isSubmitter",
|
||||||
|
Left: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.ResourceField,
|
||||||
|
Field: "Submitter",
|
||||||
|
},
|
||||||
|
Right: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.SubjectField,
|
||||||
|
Field: "ID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PresetDeleteSubmitter: &restrict.Permission{
|
||||||
|
Action: ActionDelete,
|
||||||
|
Conditions: restrict.Conditions{
|
||||||
|
&SubmitterEqualCondition{
|
||||||
|
ID: "isSubmitter",
|
||||||
|
Left: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.ResourceField,
|
||||||
|
Field: "Submitter",
|
||||||
|
},
|
||||||
|
Right: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.SubjectField,
|
||||||
|
Field: "ID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PresetShareSubmitter: &restrict.Permission{
|
||||||
|
Action: ActionShare,
|
||||||
|
Conditions: restrict.Conditions{
|
||||||
|
&SubmitterEqualCondition{
|
||||||
|
ID: "isSubmitter",
|
||||||
|
Left: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.ResourceField,
|
||||||
|
Field: "Submitter",
|
||||||
|
},
|
||||||
|
Right: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.SubjectField,
|
||||||
|
Field: "ID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PresetReadShared: &restrict.Permission{
|
||||||
|
Action: ActionRead,
|
||||||
|
Conditions: restrict.Conditions{
|
||||||
|
&restrict.EqualCondition{
|
||||||
|
ID: "isOwner",
|
||||||
|
Left: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.ResourceField,
|
||||||
|
Field: "ID",
|
||||||
|
},
|
||||||
|
Right: &restrict.ValueDescriptor{
|
||||||
|
Source: restrict.SubjectField,
|
||||||
|
Field: "EntityID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
257
pkg/rbac/rbac.go
257
pkg/rbac/rbac.go
|
@ -3,8 +3,6 @@ package rbac
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"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"
|
||||||
|
@ -34,6 +32,7 @@ const (
|
||||||
PresetUpdateOwn = "updateOwn"
|
PresetUpdateOwn = "updateOwn"
|
||||||
PresetDeleteOwn = "deleteOwn"
|
PresetDeleteOwn = "deleteOwn"
|
||||||
PresetReadShared = "readShared"
|
PresetReadShared = "readShared"
|
||||||
|
PresetReadSharedInMap = "readSharedInMap"
|
||||||
PresetShareOwn = "shareOwn"
|
PresetShareOwn = "shareOwn"
|
||||||
|
|
||||||
PresetUpdateSubmitter = "updateSubmitter"
|
PresetUpdateSubmitter = "updateSubmitter"
|
||||||
|
@ -91,212 +90,6 @@ var (
|
||||||
ErrNotAuthorized = errors.New("not authorized")
|
ErrNotAuthorized = errors.New("not authorized")
|
||||||
)
|
)
|
||||||
|
|
||||||
var policy = &restrict.PolicyDefinition{
|
|
||||||
Roles: restrict.Roles{
|
|
||||||
RoleUser: {
|
|
||||||
Description: "An authenticated user",
|
|
||||||
Grants: restrict.GrantsMap{
|
|
||||||
ResourceIncident: {
|
|
||||||
&restrict.Permission{Action: ActionRead},
|
|
||||||
&restrict.Permission{Action: ActionCreate},
|
|
||||||
&restrict.Permission{Preset: PresetUpdateOwn},
|
|
||||||
&restrict.Permission{Preset: PresetDeleteOwn},
|
|
||||||
&restrict.Permission{Preset: PresetShareOwn},
|
|
||||||
},
|
|
||||||
ResourceCall: {
|
|
||||||
&restrict.Permission{Action: ActionRead},
|
|
||||||
&restrict.Permission{Action: ActionCreate},
|
|
||||||
&restrict.Permission{Preset: PresetUpdateSubmitter},
|
|
||||||
&restrict.Permission{Preset: PresetDeleteSubmitter},
|
|
||||||
&restrict.Permission{Action: ActionShare},
|
|
||||||
},
|
|
||||||
ResourceTalkgroup: {
|
|
||||||
&restrict.Permission{Action: ActionRead},
|
|
||||||
},
|
|
||||||
ResourceShare: {
|
|
||||||
&restrict.Permission{Action: ActionRead},
|
|
||||||
&restrict.Permission{Action: ActionCreate},
|
|
||||||
&restrict.Permission{Preset: PresetUpdateOwn},
|
|
||||||
&restrict.Permission{Preset: PresetDeleteOwn},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RoleSubmitter: {
|
|
||||||
Description: "A role that can submit calls",
|
|
||||||
Grants: restrict.GrantsMap{
|
|
||||||
ResourceCall: {
|
|
||||||
&restrict.Permission{Action: ActionCreate},
|
|
||||||
},
|
|
||||||
ResourceTalkgroup: {
|
|
||||||
// for learning TGs
|
|
||||||
&restrict.Permission{Action: ActionCreate},
|
|
||||||
&restrict.Permission{Action: ActionUpdate},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RoleShareGuest: {
|
|
||||||
Description: "Someone who has a valid share link",
|
|
||||||
Grants: restrict.GrantsMap{
|
|
||||||
ResourceCall: {
|
|
||||||
&restrict.Permission{Preset: PresetReadShared},
|
|
||||||
},
|
|
||||||
ResourceIncident: {
|
|
||||||
&restrict.Permission{Preset: PresetReadShared},
|
|
||||||
},
|
|
||||||
ResourceTalkgroup: {
|
|
||||||
&restrict.Permission{Action: ActionRead},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RoleAdmin: {
|
|
||||||
Parents: []string{RoleUser},
|
|
||||||
Grants: restrict.GrantsMap{
|
|
||||||
ResourceIncident: {
|
|
||||||
&restrict.Permission{Action: ActionUpdate},
|
|
||||||
&restrict.Permission{Action: ActionDelete},
|
|
||||||
&restrict.Permission{Action: ActionShare},
|
|
||||||
},
|
|
||||||
ResourceCall: {
|
|
||||||
&restrict.Permission{Action: ActionUpdate},
|
|
||||||
&restrict.Permission{Action: ActionDelete},
|
|
||||||
&restrict.Permission{Action: ActionShare},
|
|
||||||
},
|
|
||||||
ResourceTalkgroup: {
|
|
||||||
&restrict.Permission{Action: ActionUpdate},
|
|
||||||
&restrict.Permission{Action: ActionCreate},
|
|
||||||
&restrict.Permission{Action: ActionDelete},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RoleSystem: {
|
|
||||||
Parents: []string{RoleSystem},
|
|
||||||
},
|
|
||||||
RolePublic: {
|
|
||||||
/*
|
|
||||||
Grants: restrict.GrantsMap{
|
|
||||||
ResourceShare: {
|
|
||||||
&restrict.Permission{Action: ActionRead},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PermissionPresets: restrict.PermissionPresets{
|
|
||||||
PresetUpdateOwn: &restrict.Permission{
|
|
||||||
Action: ActionUpdate,
|
|
||||||
Conditions: restrict.Conditions{
|
|
||||||
&restrict.EqualCondition{
|
|
||||||
ID: "isOwner",
|
|
||||||
Left: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.ResourceField,
|
|
||||||
Field: "Owner",
|
|
||||||
},
|
|
||||||
Right: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.SubjectField,
|
|
||||||
Field: "ID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PresetDeleteOwn: &restrict.Permission{
|
|
||||||
Action: ActionDelete,
|
|
||||||
Conditions: restrict.Conditions{
|
|
||||||
&restrict.EqualCondition{
|
|
||||||
ID: "isOwner",
|
|
||||||
Left: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.ResourceField,
|
|
||||||
Field: "Owner",
|
|
||||||
},
|
|
||||||
Right: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.SubjectField,
|
|
||||||
Field: "ID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PresetShareOwn: &restrict.Permission{
|
|
||||||
Action: ActionShare,
|
|
||||||
Conditions: restrict.Conditions{
|
|
||||||
&restrict.EqualCondition{
|
|
||||||
ID: "isOwner",
|
|
||||||
Left: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.ResourceField,
|
|
||||||
Field: "Owner",
|
|
||||||
},
|
|
||||||
Right: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.SubjectField,
|
|
||||||
Field: "ID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PresetUpdateSubmitter: &restrict.Permission{
|
|
||||||
Action: ActionUpdate,
|
|
||||||
Conditions: restrict.Conditions{
|
|
||||||
&SubmitterEqualCondition{
|
|
||||||
ID: "isSubmitter",
|
|
||||||
Left: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.ResourceField,
|
|
||||||
Field: "Submitter",
|
|
||||||
},
|
|
||||||
Right: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.SubjectField,
|
|
||||||
Field: "ID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PresetDeleteSubmitter: &restrict.Permission{
|
|
||||||
Action: ActionDelete,
|
|
||||||
Conditions: restrict.Conditions{
|
|
||||||
&SubmitterEqualCondition{
|
|
||||||
ID: "isSubmitter",
|
|
||||||
Left: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.ResourceField,
|
|
||||||
Field: "Submitter",
|
|
||||||
},
|
|
||||||
Right: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.SubjectField,
|
|
||||||
Field: "ID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PresetShareSubmitter: &restrict.Permission{
|
|
||||||
Action: ActionShare,
|
|
||||||
Conditions: restrict.Conditions{
|
|
||||||
&SubmitterEqualCondition{
|
|
||||||
ID: "isSubmitter",
|
|
||||||
Left: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.ResourceField,
|
|
||||||
Field: "Submitter",
|
|
||||||
},
|
|
||||||
Right: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.SubjectField,
|
|
||||||
Field: "ID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PresetReadShared: &restrict.Permission{
|
|
||||||
Action: ActionRead,
|
|
||||||
Conditions: restrict.Conditions{
|
|
||||||
&restrict.EqualCondition{
|
|
||||||
ID: "isOwner",
|
|
||||||
Left: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.ContextField,
|
|
||||||
Field: "Owner",
|
|
||||||
},
|
|
||||||
Right: &restrict.ValueDescriptor{
|
|
||||||
Source: restrict.SubjectField,
|
|
||||||
Field: "ID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type checkOptions struct {
|
type checkOptions struct {
|
||||||
actions []string
|
actions []string
|
||||||
context restrict.Context
|
context restrict.Context
|
||||||
|
@ -398,51 +191,3 @@ func (s *SystemServiceSubject) GetName() string {
|
||||||
func (s *SystemServiceSubject) GetRoles() []string {
|
func (s *SystemServiceSubject) GetRoles() []string {
|
||||||
return []string{RoleSystem}
|
return []string{RoleSystem}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
SubmitterEqualConditionType = "SUBMITTER_EQUAL"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SubmitterEqualCondition struct {
|
|
||||||
ID string `json:"name,omitempty" yaml:"name,omitempty"`
|
|
||||||
Left *restrict.ValueDescriptor `json:"left" yaml:"left"`
|
|
||||||
Right *restrict.ValueDescriptor `json:"right" yaml:"right"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubmitterEqualCondition) Type() string {
|
|
||||||
return SubmitterEqualConditionType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SubmitterEqualCondition) Check(r *restrict.AccessRequest) error {
|
|
||||||
left, err := c.Left.GetValue(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
right, err := c.Right.GetValue(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
lVal := reflect.ValueOf(left)
|
|
||||||
rVal := reflect.ValueOf(right)
|
|
||||||
|
|
||||||
// deref Left. this is the difference between us and EqualCondition
|
|
||||||
for lVal.Kind() == reflect.Pointer {
|
|
||||||
lVal = lVal.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !lVal.IsValid() || !reflect.DeepEqual(rVal.Interface(), lVal.Interface()) {
|
|
||||||
return restrict.NewConditionNotSatisfiedError(c, r, fmt.Errorf("values \"%v\" and \"%v\" are not equal", left, right))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SubmitterEqualConditionFactory() restrict.Condition {
|
|
||||||
return new(SubmitterEqualCondition)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
restrict.RegisterConditionFactory(SubmitterEqualConditionType, SubmitterEqualConditionFactory)
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,43 +18,44 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type APIRoot interface {
|
|
||||||
API
|
|
||||||
Shares() ShareAPI
|
|
||||||
ShareSubroutes(chi.Router)
|
|
||||||
}
|
|
||||||
|
|
||||||
type API interface {
|
type API interface {
|
||||||
Subrouter() http.Handler
|
Subrouter() http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShareableAPI interface {
|
type APIRoot interface {
|
||||||
API
|
API
|
||||||
GETSubroutes(chi.Router)
|
ShareRouter() http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
type api struct {
|
type api struct {
|
||||||
baseURL *url.URL
|
baseURL *url.URL
|
||||||
shares ShareAPI
|
shares *shareAPI
|
||||||
tgs API
|
tgs *talkgroupAPI
|
||||||
calls ShareableAPI
|
calls *callsAPI
|
||||||
users API
|
users *usersAPI
|
||||||
incidents ShareableAPI
|
incidents *incidentsAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *api) Shares() ShareAPI {
|
func (a *api) ShareRouter() http.Handler {
|
||||||
return a.shares
|
return a.shares.RootRouter()
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(baseURL url.URL) *api {
|
func New(baseURL url.URL) *api {
|
||||||
s := &api{
|
s := &api{
|
||||||
baseURL: &baseURL,
|
baseURL: &baseURL,
|
||||||
shares: newShareAPI(&baseURL),
|
|
||||||
tgs: new(talkgroupAPI),
|
tgs: new(talkgroupAPI),
|
||||||
calls: new(callsAPI),
|
calls: new(callsAPI),
|
||||||
incidents: newIncidentsAPI(&baseURL),
|
incidents: newIncidentsAPI(&baseURL),
|
||||||
users: new(usersAPI),
|
users: new(usersAPI),
|
||||||
}
|
}
|
||||||
|
s.shares = newShareAPI(&baseURL,
|
||||||
|
ShareHandlers{
|
||||||
|
ShareRequestCall: s.calls.shareCallRoute,
|
||||||
|
ShareRequestCallDL: s.calls.shareCallDLRoute,
|
||||||
|
ShareRequestIncident: s.incidents.getIncident,
|
||||||
|
ShareRequestIncidentM3U: s.incidents.getCallsM3U,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
@ -71,11 +72,6 @@ func (a *api) Subrouter() http.Handler {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *api) ShareSubroutes(r chi.Router) {
|
|
||||||
r.Route("/calls", a.calls.GETSubroutes)
|
|
||||||
r.Route("/incidents", a.incidents.GETSubroutes)
|
|
||||||
}
|
|
||||||
|
|
||||||
type errResponse struct {
|
type errResponse struct {
|
||||||
Err error `json:"-"`
|
Err error `json:"-"`
|
||||||
Code int `json:"-"`
|
Code int `json:"-"`
|
||||||
|
|
|
@ -30,22 +30,20 @@ type callsAPI struct {
|
||||||
func (ca *callsAPI) Subrouter() http.Handler {
|
func (ca *callsAPI) Subrouter() http.Handler {
|
||||||
r := chi.NewMux()
|
r := chi.NewMux()
|
||||||
|
|
||||||
ca.GETSubroutes(r)
|
r.Get(`/{call:[a-f0-9-]+}`, ca.getAudioRoute)
|
||||||
|
r.Get(`/{call:[a-f0-9-]+}/{download:download}`, ca.getAudioRoute)
|
||||||
r.Post(`/`, ca.listCalls)
|
r.Post(`/`, ca.listCalls)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ca *callsAPI) GETSubroutes(r chi.Router) {
|
type getAudioParams struct {
|
||||||
r.Get(`/{call:[a-f0-9-]+}`, ca.getAudio)
|
|
||||||
r.Get(`/{call:[a-f0-9-]+}/{download:download}`, ca.getAudio)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *callsAPI) getAudio(w http.ResponseWriter, r *http.Request) {
|
|
||||||
p := struct {
|
|
||||||
CallID *uuid.UUID `param:"call"`
|
CallID *uuid.UUID `param:"call"`
|
||||||
Download *string `param:"download"`
|
Download *string `param:"download"`
|
||||||
}{}
|
}
|
||||||
|
|
||||||
|
func (ca *callsAPI) getAudioRoute(w http.ResponseWriter, r *http.Request) {
|
||||||
|
p := getAudioParams{}
|
||||||
|
|
||||||
err := decodeParams(&p, r)
|
err := decodeParams(&p, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -53,6 +51,10 @@ func (ca *callsAPI) getAudio(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ca.getAudio(p, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ca *callsAPI) getAudio(p getAudioParams, w http.ResponseWriter, r *http.Request) {
|
||||||
if p.CallID == nil {
|
if p.CallID == nil {
|
||||||
wErr(w, r, badRequest(ErrNoCall))
|
wErr(w, r, badRequest(ErrNoCall))
|
||||||
return
|
return
|
||||||
|
@ -99,6 +101,23 @@ func (ca *callsAPI) getAudio(w http.ResponseWriter, r *http.Request) {
|
||||||
_, _ = w.Write(call.AudioBlob)
|
_, _ = w.Write(call.AudioBlob)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ca *callsAPI) shareCallRoute(id uuid.UUID, w http.ResponseWriter, r *http.Request) {
|
||||||
|
p := getAudioParams{
|
||||||
|
CallID: &id,
|
||||||
|
}
|
||||||
|
|
||||||
|
ca.getAudio(p, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ca *callsAPI) shareCallDLRoute(id uuid.UUID, w http.ResponseWriter, r *http.Request) {
|
||||||
|
p := getAudioParams{
|
||||||
|
CallID: &id,
|
||||||
|
Download: common.PtrTo("download"),
|
||||||
|
}
|
||||||
|
|
||||||
|
ca.getAudio(p, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
func (ca *callsAPI) listCalls(w http.ResponseWriter, r *http.Request) {
|
func (ca *callsAPI) listCalls(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
cSt := callstore.FromCtx(ctx)
|
cSt := callstore.FromCtx(ctx)
|
||||||
|
|
|
@ -22,14 +22,15 @@ type incidentsAPI struct {
|
||||||
baseURL *url.URL
|
baseURL *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIncidentsAPI(baseURL *url.URL) ShareableAPI {
|
func newIncidentsAPI(baseURL *url.URL) *incidentsAPI {
|
||||||
return &incidentsAPI{baseURL}
|
return &incidentsAPI{baseURL}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia *incidentsAPI) Subrouter() http.Handler {
|
func (ia *incidentsAPI) Subrouter() http.Handler {
|
||||||
r := chi.NewMux()
|
r := chi.NewMux()
|
||||||
|
|
||||||
ia.GETSubroutes(r)
|
r.Get(`/{id:[a-f0-9-]+}`, ia.getIncidentRoute)
|
||||||
|
r.Get(`/{id:[a-f0-9-]+}.m3u`, ia.getCallsM3URoute)
|
||||||
r.Post(`/new`, ia.createIncident)
|
r.Post(`/new`, ia.createIncident)
|
||||||
r.Post(`/`, ia.listIncidents)
|
r.Post(`/`, ia.listIncidents)
|
||||||
r.Post(`/{id:[a-f0-9-]+}/calls`, ia.postCalls)
|
r.Post(`/{id:[a-f0-9-]+}/calls`, ia.postCalls)
|
||||||
|
@ -41,11 +42,6 @@ func (ia *incidentsAPI) Subrouter() http.Handler {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia *incidentsAPI) GETSubroutes(r chi.Router) {
|
|
||||||
r.Get(`/{id:[a-f0-9-]+}`, ia.getIncident)
|
|
||||||
r.Get(`/{id:[a-f0-9-]+}.m3u`, ia.getCallsM3U)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ia *incidentsAPI) listIncidents(w http.ResponseWriter, r *http.Request) {
|
func (ia *incidentsAPI) listIncidents(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
incs := incstore.FromCtx(ctx)
|
incs := incstore.FromCtx(ctx)
|
||||||
|
@ -91,15 +87,18 @@ func (ia *incidentsAPI) createIncident(w http.ResponseWriter, r *http.Request) {
|
||||||
respond(w, r, inc)
|
respond(w, r, inc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia *incidentsAPI) getIncident(w http.ResponseWriter, r *http.Request) {
|
func (ia *incidentsAPI) getIncidentRoute(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
|
||||||
incs := incstore.FromCtx(ctx)
|
|
||||||
|
|
||||||
id, err := idOnlyParam(w, r)
|
id, err := idOnlyParam(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ia.getIncident(id, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ia *incidentsAPI) getIncident(id uuid.UUID, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
incs := incstore.FromCtx(ctx)
|
||||||
inc, err := incs.Incident(ctx, id)
|
inc, err := incs.Incident(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wErr(w, r, autoError(err))
|
wErr(w, r, autoError(err))
|
||||||
|
@ -189,16 +188,20 @@ func (ia *incidentsAPI) postCalls(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia *incidentsAPI) getCallsM3U(w http.ResponseWriter, r *http.Request) {
|
func (ia *incidentsAPI) getCallsM3URoute(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
|
||||||
incs := incstore.FromCtx(ctx)
|
|
||||||
tgst := tgstore.FromCtx(ctx)
|
|
||||||
|
|
||||||
id, err := idOnlyParam(w, r)
|
id, err := idOnlyParam(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ia.getCallsM3U(id, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ia *incidentsAPI) getCallsM3U(id uuid.UUID, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
incs := incstore.FromCtx(ctx)
|
||||||
|
tgst := tgstore.FromCtx(ctx)
|
||||||
|
|
||||||
inc, err := incs.Incident(ctx, id)
|
inc, err := incs.Incident(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wErr(w, r, autoError(err))
|
wErr(w, r, autoError(err))
|
||||||
|
|
|
@ -2,10 +2,8 @@ package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/forms"
|
"dynatron.me/x/stillbox/internal/forms"
|
||||||
|
@ -13,6 +11,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/shares"
|
"dynatron.me/x/stillbox/pkg/shares"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -23,6 +22,7 @@ type ShareRequestType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ShareRequestCall ShareRequestType = "call"
|
ShareRequestCall ShareRequestType = "call"
|
||||||
|
ShareRequestCallDL ShareRequestType = "callDL"
|
||||||
ShareRequestIncident ShareRequestType = "incident"
|
ShareRequestIncident ShareRequestType = "incident"
|
||||||
ShareRequestIncidentM3U ShareRequestType = "m3u"
|
ShareRequestIncidentM3U ShareRequestType = "m3u"
|
||||||
)
|
)
|
||||||
|
@ -36,39 +36,17 @@ func (rt ShareRequestType) IsValid() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShareHandlers map[shares.EntityType]http.Handler
|
type HandlerFunc func(uuid.UUID, http.ResponseWriter, *http.Request)
|
||||||
|
type ShareHandlers map[ShareRequestType]HandlerFunc
|
||||||
type shareAPI struct {
|
type shareAPI struct {
|
||||||
baseURL *url.URL
|
baseURL *url.URL
|
||||||
|
shnd ShareHandlers
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShareAPI interface {
|
func newShareAPI(baseURL *url.URL, shnd ShareHandlers) *shareAPI {
|
||||||
API
|
|
||||||
ShareMiddleware() func(http.Handler) http.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func newShareAPI(baseURL *url.URL) ShareAPI {
|
|
||||||
return &shareAPI{
|
return &shareAPI{
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
}
|
shnd: shnd,
|
||||||
}
|
|
||||||
|
|
||||||
func (a *shareAPI) ShareMiddleware() func(http.Handler) http.Handler {
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !strings.HasPrefix(r.URL.Path, "/share/") {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nr, err := a.getShare(r)
|
|
||||||
if err != nil {
|
|
||||||
wErr(w, r, autoError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next.ServeHTTP(w, nr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.HandlerFunc(fn)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,10 +59,12 @@ func (sa *shareAPI) Subrouter() http.Handler {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
//func (sa *shareAPI) PublicRoutes(r chi.Router) http.Handler {
|
func (sa *shareAPI) RootRouter() http.Handler {
|
||||||
// r.Get(`/{type}/{id:[A-Za-z0-9_-]{20,}}`, sa.getShare)
|
r := chi.NewMux()
|
||||||
// r.Get(`/{type}/{id:[A-Za-z0-9_-]{20,}}*`, sa.getShare)
|
|
||||||
//}
|
r.Get("/{type}/{shareId:[A-Za-z0-9_-]{20,}}", sa.routeShare)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func (sa *shareAPI) createShare(w http.ResponseWriter, r *http.Request) {
|
func (sa *shareAPI) createShare(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
@ -107,10 +87,7 @@ func (sa *shareAPI) createShare(w http.ResponseWriter, r *http.Request) {
|
||||||
respond(w, r, sh)
|
respond(w, r, sh)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sa *shareAPI) deleteShare(w http.ResponseWriter, r *http.Request) {
|
func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
|
||||||
|
|
||||||
func (sa *shareAPI) getShare(r *http.Request) (*http.Request, error) {
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
shs := shares.FromCtx(ctx)
|
shs := shares.FromCtx(ctx)
|
||||||
|
|
||||||
|
@ -121,50 +98,34 @@ func (sa *shareAPI) getShare(r *http.Request) (*http.Request, error) {
|
||||||
|
|
||||||
err := decodeParams(¶ms, r)
|
err := decodeParams(¶ms, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
wErr(w, r, autoError(err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rType := ShareRequestType(params.Type)
|
rType := ShareRequestType(params.Type)
|
||||||
id := params.ID
|
id := params.ID
|
||||||
|
|
||||||
if !rType.IsValid() {
|
if !rType.IsValid() {
|
||||||
return nil, ErrBadShare
|
wErr(w, r, autoError(ErrBadShare))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sh, err := shs.GetShare(ctx, id)
|
sh, err := shs.GetShare(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
wErr(w, r, autoError(err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if sh.Expiration != nil && sh.Expiration.Time().Before(time.Now()) {
|
if sh.Expiration != nil && sh.Expiration.Time().Before(time.Now()) {
|
||||||
return nil, shares.ErrNoShare
|
wErr(w, r, autoError(shares.ErrNoShare))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = rbac.CtxWithSubject(ctx, sh)
|
ctx = rbac.CtxWithSubject(ctx, sh)
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
switch rType {
|
sa.shnd[rType](sh.EntityID, w, r)
|
||||||
case ShareRequestCall, ShareRequestIncident:
|
|
||||||
r.URL.Path += fmt.Sprintf("/%s/%s", rType, sh.EntityID.String())
|
|
||||||
case ShareRequestIncidentM3U:
|
|
||||||
r.URL.Path = fmt.Sprintf("/incident/%s.m3u", sh.EntityID.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// idOnlyParam checks for a sole URL parameter, id, and writes an errorif this fails.
|
func (sa *shareAPI) deleteShare(w http.ResponseWriter, r *http.Request) {
|
||||||
func shareParams(w http.ResponseWriter, r *http.Request) (rType ShareRequestType, rID string, err error) {
|
|
||||||
params := struct {
|
|
||||||
Type string `param:"type"`
|
|
||||||
ID string `param:"id"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
err = decodeParams(¶ms, r)
|
|
||||||
if err != nil {
|
|
||||||
wErr(w, r, badRequest(err))
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ShareRequestType(params.Type), params.ID, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ func (s *Server) setupRoutes() {
|
||||||
|
|
||||||
s.installPprof()
|
s.installPprof()
|
||||||
|
|
||||||
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
// authenticated routes
|
// authenticated routes
|
||||||
r.Use(s.auth.VerifyMiddleware(), s.auth.AuthMiddleware())
|
r.Use(s.auth.VerifyMiddleware(), s.auth.AuthMiddleware())
|
||||||
|
@ -41,11 +40,6 @@ func (s *Server) setupRoutes() {
|
||||||
r.Mount("/api", s.rest.Subrouter())
|
r.Mount("/api", s.rest.Subrouter())
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Route("/share/{type}/{shareId:[A-Za-z0-9_-]{20,}}", func(r chi.Router) {
|
|
||||||
r.Use(s.rest.Shares().ShareMiddleware())
|
|
||||||
s.rest.ShareSubroutes(r)
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
s.rateLimit(r)
|
s.rateLimit(r)
|
||||||
r.Use(render.SetContentType(render.ContentTypeJSON))
|
r.Use(render.SetContentType(render.ContentTypeJSON))
|
||||||
|
@ -58,6 +52,7 @@ func (s *Server) setupRoutes() {
|
||||||
s.rateLimit(r)
|
s.rateLimit(r)
|
||||||
r.Use(render.SetContentType(render.ContentTypeJSON))
|
r.Use(render.SetContentType(render.ContentTypeJSON))
|
||||||
s.auth.PublicRoutes(r)
|
s.auth.PublicRoutes(r)
|
||||||
|
r.Mount("/share", s.rest.ShareRouter())
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
|
@ -68,10 +63,6 @@ func (s *Server) setupRoutes() {
|
||||||
|
|
||||||
s.clientRoute(r, clientRoot)
|
s.clientRoute(r, clientRoot)
|
||||||
})
|
})
|
||||||
chi.Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
|
|
||||||
fmt.Printf("[%s]: '%s' has %d middlewares\n", method, route, len(middlewares))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCtxStores is a middleware that installs all stores in the request context.
|
// WithCtxStores is a middleware that installs all stores in the request context.
|
||||||
|
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
@ -145,6 +146,13 @@ func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
||||||
}))
|
}))
|
||||||
srv.setupRoutes()
|
srv.setupRoutes()
|
||||||
|
|
||||||
|
if os.Getenv("STILLBOX_DUMP_ROUTES") == "true" {
|
||||||
|
chi.Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
|
||||||
|
fmt.Printf("[%s]: '%s' has %d middlewares\n", method, route, len(middlewares))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return srv, nil
|
return srv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue