feat(annotations): authorization service (#21665)

* feat(annotations): added authz service for annotations and streams

* fix: tests and adjustments

* chore: misc cleanup

* chore: update number in authz comment

Co-authored-by: Daniel Moran <danxmoran@gmail.com>

* chore: update number in authz comment

Co-authored-by: Daniel Moran <danxmoran@gmail.com>

* chore: update number in authz comment

Co-authored-by: Daniel Moran <danxmoran@gmail.com>

* fix: address review comments

* feat: added post-list filter for fine-grained auth

Co-authored-by: Daniel Moran <danxmoran@gmail.com>
pull/21675/head
William Baker 2021-06-11 17:39:51 -04:00 committed by GitHub
parent 40c7d69415
commit 8ee59f4bf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1324 additions and 125 deletions

View File

@ -68,9 +68,9 @@ type AnnotationService interface {
// CreateAnnotations creates annotations.
CreateAnnotations(ctx context.Context, orgID platform.ID, create []AnnotationCreate) ([]AnnotationEvent, error)
// ListAnnotations lists all annotations matching the filter.
ListAnnotations(ctx context.Context, orgID platform.ID, filter AnnotationListFilter) (ReadAnnotations, error)
ListAnnotations(ctx context.Context, orgID platform.ID, filter AnnotationListFilter) ([]StoredAnnotation, error)
// GetAnnotation gets an annotation by id.
GetAnnotation(ctx context.Context, id platform.ID) (*AnnotationEvent, error)
GetAnnotation(ctx context.Context, id platform.ID) (*StoredAnnotation, error)
// DeleteAnnotations deletes annotations matching the filter.
DeleteAnnotations(ctx context.Context, orgID platform.ID, delete AnnotationDeleteFilter) error
// DeleteAnnotation deletes an annotation by id.
@ -79,13 +79,16 @@ type AnnotationService interface {
UpdateAnnotation(ctx context.Context, id platform.ID, update AnnotationCreate) (*AnnotationEvent, error)
// ListStreams lists all streams matching the filter.
ListStreams(ctx context.Context, orgID platform.ID, filter StreamListFilter) ([]ReadStream, error)
ListStreams(ctx context.Context, orgID platform.ID, filter StreamListFilter) ([]StoredStream, error)
// CreateOrUpdateStream creates or updates the matching stream by name.
CreateOrUpdateStream(ctx context.Context, stream Stream) (*ReadStream, error)
CreateOrUpdateStream(ctx context.Context, orgID platform.ID, stream Stream) (*ReadStream, error)
// GetStream gets a stream by id. Currently this is only used for authorization, and there are no
// API routes for getting a single stream by ID.
GetStream(ctx context.Context, id platform.ID) (*StoredStream, error)
// UpdateStream updates the stream by the ID.
UpdateStream(ctx context.Context, id platform.ID, stream Stream) (*ReadStream, error)
// DeleteStreams deletes one or more streams by name.
DeleteStreams(ctx context.Context, delete BasicStream) error
DeleteStreams(ctx context.Context, orgID platform.ID, delete BasicStream) error
// DeleteStreamByID deletes the stream metadata by id.
DeleteStreamByID(ctx context.Context, id platform.ID) error
}
@ -116,7 +119,7 @@ type StoredAnnotation struct {
Message string `db:"message"` // Message is a longer description of the annotated event.
Stickers []string `db:"stickers"` // Stickers are additional labels to group annotations by.
Duration string `db:"duration"` // Duration is the time range (with zone) of an annotated event.
Lower string `db:"lower"` // Lower is the time an annotated event beings.
Lower string `db:"lower"` // Lower is the time an annotated event begins.
Upper string `db:"upper"` // Upper is the time an annotated event ends.
}

View File

@ -3,6 +3,7 @@ package transport
import (
"encoding/json"
"net/http"
"strings"
"time"
"github.com/go-chi/chi"
@ -65,7 +66,13 @@ func (h *AnnotationHandler) handleGetAnnotations(w http.ResponseWriter, r *http.
return
}
l, err := h.annotationService.ListAnnotations(ctx, *o, *f)
s, err := h.annotationService.ListAnnotations(ctx, *o, *f)
if err != nil {
h.api.Err(w, r, err)
return
}
l, err := storedAnnotationsToReadAnnotations(s)
if err != nil {
h.api.Err(w, r, err)
return
@ -106,13 +113,19 @@ func (h *AnnotationHandler) handleGetAnnotation(w http.ResponseWriter, r *http.R
return
}
a, err := h.annotationService.GetAnnotation(ctx, *id)
s, err := h.annotationService.GetAnnotation(ctx, *id)
if err != nil {
h.api.Err(w, r, err)
return
}
h.api.Respond(w, r, http.StatusOK, a)
c, err := storedAnnotationToEvent(s)
if err != nil {
h.api.Err(w, r, err)
return
}
h.api.Respond(w, r, http.StatusOK, c)
}
func (h *AnnotationHandler) handleDeleteAnnotation(w http.ResponseWriter, r *http.Request) {
@ -234,3 +247,68 @@ func decodeUpdateAnnotationRequest(r *http.Request) (*influxdb.AnnotationCreate,
return u, nil
}
func storedAnnotationsToReadAnnotations(s []influxdb.StoredAnnotation) (influxdb.ReadAnnotations, error) {
r := influxdb.ReadAnnotations{}
for _, val := range s {
stickers, err := stickerSliceToMap(val.Stickers)
if err != nil {
return nil, err
}
r[val.StreamTag] = append(r[val.StreamTag], influxdb.ReadAnnotation{
ID: val.ID,
Summary: val.Summary,
Message: val.Message,
Stickers: stickers,
StartTime: val.Lower,
EndTime: val.Upper,
})
}
return r, nil
}
func storedAnnotationToEvent(s *influxdb.StoredAnnotation) (*influxdb.AnnotationEvent, error) {
st, err := tStringToPointer(s.Lower)
if err != nil {
return nil, err
}
et, err := tStringToPointer(s.Upper)
if err != nil {
return nil, err
}
stickers, err := stickerSliceToMap(s.Stickers)
if err != nil {
return nil, err
}
return &influxdb.AnnotationEvent{
ID: s.ID,
AnnotationCreate: influxdb.AnnotationCreate{
StreamTag: s.StreamTag,
Summary: s.Summary,
Message: s.Message,
Stickers: stickers,
EndTime: et,
StartTime: st,
},
}, nil
}
func stickerSliceToMap(stickers []string) (map[string]string, error) {
stickerMap := map[string]string{}
for i := range stickers {
sticks := strings.SplitN(stickers[i], "=", 2)
if len(sticks) < 2 {
return nil, invalidStickerError(stickers[i])
}
stickerMap[sticks[0]] = sticks[1]
}
return stickerMap, nil
}

View File

@ -16,6 +16,8 @@ var (
testCreateAnnotation = influxdb.AnnotationCreate{
StreamTag: "sometag",
Summary: "testing the api",
Message: "stored annotation message",
Stickers: map[string]string{"val1": "sticker1", "val2": "sticker2"},
EndTime: &now,
StartTime: &now,
}
@ -32,6 +34,31 @@ var (
testReadAnnotation2 = influxdb.ReadAnnotation{
ID: *influxdbtesting.IDPtr(2),
}
testStoredAnnotation = influxdb.StoredAnnotation{
ID: *id,
OrgID: *orgID,
StreamID: *influxdbtesting.IDPtr(3),
StreamTag: "sometag",
Summary: "testing the api",
Message: "stored annotation message",
Stickers: []string{"val1=sticker1", "val2=sticker2"},
Lower: now.Format(time.RFC3339),
Upper: now.Format(time.RFC3339),
}
testReadAnnotations = influxdb.ReadAnnotations{
"sometag": []influxdb.ReadAnnotation{
{
ID: testStoredAnnotation.ID,
Summary: testStoredAnnotation.Summary,
Message: testStoredAnnotation.Message,
Stickers: map[string]string{"val1": "sticker1", "val2": "sticker2"},
EndTime: testStoredAnnotation.Lower,
StartTime: testStoredAnnotation.Upper,
},
},
}
)
func TestAnnotationRouter(t *testing.T) {
@ -72,9 +99,15 @@ func TestAnnotationRouter(t *testing.T) {
EndTime: &now,
},
}).
Return(influxdb.ReadAnnotations{
"stream1": []influxdb.ReadAnnotation{testReadAnnotation1},
"stream2": []influxdb.ReadAnnotation{testReadAnnotation2},
Return([]influxdb.StoredAnnotation{
{
ID: testReadAnnotation1.ID,
StreamTag: "stream1",
},
{
ID: testReadAnnotation2.ID,
StreamTag: "stream2",
},
}, nil)
res := doTestRequest(t, req, http.StatusOK, true)
@ -143,7 +176,7 @@ func TestAnnotationRouter(t *testing.T) {
svc.EXPECT().
GetAnnotation(gomock.Any(), *id).
Return(&testEvent, nil)
Return(&testStoredAnnotation, nil)
res := doTestRequest(t, req, http.StatusOK, true)
@ -216,3 +249,55 @@ func TestAnnotationRouter(t *testing.T) {
}
})
}
func TestStoredAnnotationsToReadAnnotations(t *testing.T) {
t.Parallel()
got, err := storedAnnotationsToReadAnnotations([]influxdb.StoredAnnotation{testStoredAnnotation})
require.NoError(t, err)
require.Equal(t, got, testReadAnnotations)
}
func TestStoredAnnotationToEvent(t *testing.T) {
t.Parallel()
got, err := storedAnnotationToEvent(&testStoredAnnotation)
require.NoError(t, err)
require.Equal(t, got, &testEvent)
}
func TestStickerSliceToMap(t *testing.T) {
t.Parallel()
tests := []struct {
name string
stickers []string
want map[string]string
wantErr error
}{
{
"good stickers",
[]string{"good1=val1", "good2=val2"},
map[string]string{"good1": "val1", "good2": "val2"},
nil,
},
{
"bad stickers",
[]string{"this is an invalid sticker", "shouldbe=likethis"},
nil,
invalidStickerError("this is an invalid sticker"),
},
{
"no stickers",
[]string{},
map[string]string{},
nil,
},
}
for _, tt := range tests {
got, err := stickerSliceToMap(tt.stickers)
require.Equal(t, tt.want, got)
require.Equal(t, tt.wantErr, err)
}
}

View File

@ -1,6 +1,7 @@
package transport
import (
"fmt"
"net/http"
"time"
@ -40,6 +41,13 @@ var (
}
)
func invalidStickerError(s string) error {
return &errors.Error{
Code: errors.EInternal,
Msg: fmt.Sprintf("invalid sticker: %q", s),
}
}
// AnnotationsHandler is the handler for the annotation service
type AnnotationHandler struct {
chi.Router

View File

@ -28,13 +28,19 @@ func (h *AnnotationHandler) streamsRouter() http.Handler {
func (h *AnnotationHandler) handleCreateOrUpdateStream(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
o, err := platform.IDFromString(r.URL.Query().Get("orgID"))
if err != nil {
h.api.Err(w, r, errBadOrg)
return
}
u, err := decodeCreateOrUpdateStreamRequest(r)
if err != nil {
h.api.Err(w, r, err)
return
}
s, err := h.annotationService.CreateOrUpdateStream(ctx, *u)
s, err := h.annotationService.CreateOrUpdateStream(ctx, *o, *u)
if err != nil {
h.api.Err(w, r, err)
return
@ -58,19 +64,25 @@ func (h *AnnotationHandler) handleGetStreams(w http.ResponseWriter, r *http.Requ
return
}
l, err := h.annotationService.ListStreams(ctx, *o, *f)
s, err := h.annotationService.ListStreams(ctx, *o, *f)
if err != nil {
h.api.Err(w, r, err)
return
}
h.api.Respond(w, r, http.StatusOK, l)
h.api.Respond(w, r, http.StatusOK, storedStreamsToReadStreams(s))
}
// Delete stream(s) by name, capable of handling a list of names
func (h *AnnotationHandler) handleDeleteStreams(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
o, err := platform.IDFromString(r.URL.Query().Get("orgID"))
if err != nil {
h.api.Err(w, r, errBadOrg)
return
}
f, err := decodeDeleteStreamsRequest(r)
if err != nil {
h.api.Err(w, r, err)
@ -79,7 +91,7 @@ func (h *AnnotationHandler) handleDeleteStreams(w http.ResponseWriter, r *http.R
// delete all of the streams according to the filter. annotations associated with the stream
// will be deleted by the ON DELETE CASCADE relationship between streams and annotations.
if err = h.annotationService.DeleteStreams(ctx, *f); err != nil {
if err = h.annotationService.DeleteStreams(ctx, *o, *f); err != nil {
h.api.Err(w, r, err)
return
}
@ -176,3 +188,19 @@ func decodeDeleteStreamsRequest(r *http.Request) (*influxdb.BasicStream, error)
return f, nil
}
func storedStreamsToReadStreams(stored []influxdb.StoredStream) []influxdb.ReadStream {
r := make([]influxdb.ReadStream, 0, len(stored))
for _, s := range stored {
r = append(r, influxdb.ReadStream{
ID: s.ID,
Name: s.Name,
Description: s.Description,
CreatedAt: s.CreatedAt,
UpdatedAt: s.UpdatedAt,
})
}
return r
}

View File

@ -30,6 +30,24 @@ var (
CreatedAt: now,
UpdatedAt: now,
}
testStoredStream1 = influxdb.StoredStream{
ID: testReadStream1.ID,
OrgID: *orgID,
Name: testReadStream1.Name,
Description: testReadStream1.Description,
CreatedAt: testReadStream1.CreatedAt,
UpdatedAt: testReadStream1.UpdatedAt,
}
testStoredStream2 = influxdb.StoredStream{
ID: testReadStream2.ID,
OrgID: *orgID,
Name: testReadStream2.Name,
Description: testReadStream2.Description,
CreatedAt: testReadStream2.CreatedAt,
UpdatedAt: testReadStream2.UpdatedAt,
}
)
func TestStreamsRouter(t *testing.T) {
@ -46,7 +64,7 @@ func TestStreamsRouter(t *testing.T) {
req.URL.RawQuery = q.Encode()
svc.EXPECT().
CreateOrUpdateStream(gomock.Any(), testCreateStream).
CreateOrUpdateStream(gomock.Any(), *orgID, testCreateStream).
Return(testReadStream1, nil)
res := doTestRequest(t, req, http.StatusOK, true)
@ -70,8 +88,6 @@ func TestStreamsRouter(t *testing.T) {
q.Add("streamIncludes", "stream2")
req.URL.RawQuery = q.Encode()
want := []influxdb.ReadStream{*testReadStream1, *testReadStream2}
svc.EXPECT().
ListStreams(gomock.Any(), *orgID, influxdb.StreamListFilter{
StreamIncludes: []string{"stream1", "stream2"},
@ -80,14 +96,14 @@ func TestStreamsRouter(t *testing.T) {
EndTime: &now,
},
}).
Return(want, nil)
Return([]influxdb.StoredStream{testStoredStream1, testStoredStream2}, nil)
res := doTestRequest(t, req, http.StatusOK, true)
got := []influxdb.ReadStream{}
err := json.NewDecoder(res.Body).Decode(&got)
require.NoError(t, err)
require.ElementsMatch(t, want, got)
require.ElementsMatch(t, []influxdb.ReadStream{*testReadStream1, *testReadStream2}, got)
})
t.Run("delete streams (by name) happy path", func(t *testing.T) {
@ -96,12 +112,13 @@ func TestStreamsRouter(t *testing.T) {
req := newTestRequest(t, "DELETE", ts.URL+"/streams", nil)
q := req.URL.Query()
q.Add("orgID", orgStr)
q.Add("stream", "stream1")
q.Add("stream", "stream2")
req.URL.RawQuery = q.Encode()
svc.EXPECT().
DeleteStreams(gomock.Any(), influxdb.BasicStream{
DeleteStreams(gomock.Any(), *orgID, influxdb.BasicStream{
Names: []string{"stream1", "stream2"},
}).
Return(nil)
@ -141,7 +158,7 @@ func TestStreamsRouter(t *testing.T) {
})
t.Run("invalid org ids return 400 when required", func(t *testing.T) {
methods := []string{"GET"}
methods := []string{"GET", "PUT", "DELETE"}
for _, m := range methods {
t.Run(m, func(t *testing.T) {
@ -172,3 +189,10 @@ func TestStreamsRouter(t *testing.T) {
}
})
}
func TestStoredStreamsToReadStreams(t *testing.T) {
t.Parallel()
got := storedStreamsToReadStreams([]influxdb.StoredStream{testStoredStream1, testStoredStream2})
require.Equal(t, got, []influxdb.ReadStream{*testReadStream1, *testReadStream2})
}

189
authorizer/annotation.go Normal file
View File

@ -0,0 +1,189 @@
package authorizer
import (
"context"
"fmt"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
)
var _ influxdb.AnnotationService = (*AnnotationService)(nil)
// AnnotationService wraps an influxdb.AnnotationService and authorizes actions
// against it appropriately.
type AnnotationService struct {
s influxdb.AnnotationService
}
// NewAnnotationService constructs an instance of an authorizing check service
func NewAnnotationService(s influxdb.AnnotationService) *AnnotationService {
return &AnnotationService{
s: s,
}
}
// CreateAnnotations checks to see if the authorizer on context has write access for annotations for the provided orgID
func (s *AnnotationService) CreateAnnotations(ctx context.Context, orgID platform.ID, create []influxdb.AnnotationCreate) ([]influxdb.AnnotationEvent, error) {
if _, _, err := AuthorizeCreate(ctx, influxdb.AnnotationsResourceType, orgID); err != nil {
return nil, err
}
return s.s.CreateAnnotations(ctx, orgID, create)
}
// ListAnnotations checks to see if the authorizer on context has read access for annotations for the provided orgID
// and then filters the list down to only the resources that are authorized
func (s *AnnotationService) ListAnnotations(ctx context.Context, orgID platform.ID, filter influxdb.AnnotationListFilter) ([]influxdb.StoredAnnotation, error) {
if _, _, err := AuthorizeOrgReadResource(ctx, influxdb.AnnotationsResourceType, orgID); err != nil {
return nil, err
}
as, err := s.s.ListAnnotations(ctx, orgID, filter)
if err != nil {
return nil, err
}
as, _, err = AuthorizeFindAnnotations(ctx, as)
return as, err
}
// GetAnnotation checks to see if the authorizer on context has read access to the requested annotation
func (s *AnnotationService) GetAnnotation(ctx context.Context, id platform.ID) (*influxdb.StoredAnnotation, error) {
a, err := s.s.GetAnnotation(ctx, id)
if err != nil {
return nil, err
}
if _, _, err := AuthorizeRead(ctx, influxdb.AnnotationsResourceType, id, a.OrgID); err != nil {
return nil, err
}
return a, nil
}
// DeleteAnnotations checks to see if the authorizer on context has write access to the provided orgID
func (s *AnnotationService) DeleteAnnotations(ctx context.Context, orgID platform.ID, delete influxdb.AnnotationDeleteFilter) error {
if _, _, err := AuthorizeOrgWriteResource(ctx, influxdb.AnnotationsResourceType, orgID); err != nil {
return err
}
return s.s.DeleteAnnotations(ctx, orgID, delete)
}
// DeleteAnnotation checks to see if the authorizer on context has write access to the requested annotation
func (s *AnnotationService) DeleteAnnotation(ctx context.Context, id platform.ID) error {
a, err := s.s.GetAnnotation(ctx, id)
if err != nil {
return err
}
if _, _, err := AuthorizeWrite(ctx, influxdb.AnnotationsResourceType, id, a.OrgID); err != nil {
return err
}
return s.s.DeleteAnnotation(ctx, id)
}
// UpdateAnnotation checks to see if the authorizer on context has write access to the requested annotation
func (s *AnnotationService) UpdateAnnotation(ctx context.Context, id platform.ID, update influxdb.AnnotationCreate) (*influxdb.AnnotationEvent, error) {
a, err := s.s.GetAnnotation(ctx, id)
if err != nil {
return nil, err
}
if _, _, err := AuthorizeWrite(ctx, influxdb.AnnotationsResourceType, id, a.OrgID); err != nil {
return nil, err
}
return s.s.UpdateAnnotation(ctx, id, update)
}
// ListStreams checks to see if the authorizer on context has read access for streams for the provided orgID
// and then filters the list down to only the resources that are authorized
func (s *AnnotationService) ListStreams(ctx context.Context, orgID platform.ID, filter influxdb.StreamListFilter) ([]influxdb.StoredStream, error) {
if _, _, err := AuthorizeOrgReadResource(ctx, influxdb.AnnotationsResourceType, orgID); err != nil {
return nil, err
}
ss, err := s.s.ListStreams(ctx, orgID, filter)
if err != nil {
return nil, err
}
ss, _, err = AuthorizeFindStreams(ctx, ss)
return ss, err
}
// GetStream checks to see if the authorizer on context has read access to the requested stream
func (s *AnnotationService) GetStream(ctx context.Context, id platform.ID) (*influxdb.StoredStream, error) {
st, err := s.s.GetStream(ctx, id)
if err != nil {
return nil, err
}
if _, _, err := AuthorizeRead(ctx, influxdb.AnnotationsResourceType, id, st.OrgID); err != nil {
return nil, err
}
return st, nil
}
func (s *AnnotationService) CreateOrUpdateStream(ctx context.Context, orgID platform.ID, stream influxdb.Stream) (*influxdb.ReadStream, error) {
// We need to know if the request is creating a new stream, or updating an existing stream to check
// permissions appropriately
// Get the stream by name. An empty slice will be returned if the stream doesn't exist
// note: a given org can only have one stream by the same name. this constraint is enforced in the database schema
streams, err := s.s.ListStreams(ctx, orgID, influxdb.StreamListFilter{
StreamIncludes: []string{stream.Name},
})
if err != nil {
return nil, err
}
// update an already existing stream
if len(streams) == 1 {
return s.UpdateStream(ctx, streams[0].ID, stream)
}
// create a new stream if one doesn't already exist
if len(streams) == 0 {
if _, _, err := AuthorizeCreate(ctx, influxdb.AnnotationsResourceType, orgID); err != nil {
return nil, err
}
return s.s.CreateOrUpdateStream(ctx, orgID, stream)
}
// if multiple streams were returned somehow, return an error
// this should never happen, so return a server error
return nil, &errors.Error{
Code: errors.EInternal,
Msg: fmt.Sprintf("more than one stream named %q for org %q", streams[0].Name, orgID),
}
}
// UpdateStream checks to see if the authorizer on context has write access to the requested stream
func (s *AnnotationService) UpdateStream(ctx context.Context, id platform.ID, stream influxdb.Stream) (*influxdb.ReadStream, error) {
st, err := s.s.GetStream(ctx, id)
if err != nil {
return nil, err
}
if _, _, err := AuthorizeWrite(ctx, influxdb.AnnotationsResourceType, id, st.OrgID); err != nil {
return nil, err
}
return s.s.UpdateStream(ctx, id, stream)
}
// DeleteStreams checks to see if the authorizer on context has write access to the provided orgID
func (s *AnnotationService) DeleteStreams(ctx context.Context, orgID platform.ID, delete influxdb.BasicStream) error {
if _, _, err := AuthorizeOrgWriteResource(ctx, influxdb.AnnotationsResourceType, orgID); err != nil {
return err
}
return s.s.DeleteStreams(ctx, orgID, delete)
}
// DeleteStreamByID checks to see if the authorizer on context has write access to the requested stream
func (s *AnnotationService) DeleteStreamByID(ctx context.Context, id platform.ID) error {
st, err := s.s.GetStream(ctx, id)
if err != nil {
return err
}
if _, _, err := AuthorizeWrite(ctx, influxdb.AnnotationsResourceType, id, st.OrgID); err != nil {
return err
}
return s.s.DeleteStreamByID(ctx, id)
}

View File

@ -0,0 +1,725 @@
package authorizer_test
import (
"context"
"fmt"
"testing"
"github.com/golang/mock/gomock"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/authorizer"
influxdbcontext "github.com/influxdata/influxdb/v2/context"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
"github.com/influxdata/influxdb/v2/mock"
influxdbtesting "github.com/influxdata/influxdb/v2/testing"
"github.com/stretchr/testify/require"
)
var (
annOrgID1 = influxdbtesting.IDPtr(1)
annOrgID2 = influxdbtesting.IDPtr(10)
rID = influxdbtesting.IDPtr(2)
)
func Test_CreateAnnotations(t *testing.T) {
t.Parallel()
tests := []struct {
name string
wantRet []influxdb.AnnotationEvent
wantErr error
}{
{
"authorized to create annotation(s) with the specified org",
[]influxdb.AnnotationEvent{{ID: *rID}},
nil,
},
{
"not authorized to create annotation(s) with the specified org",
nil,
&errors.Error{
Msg: fmt.Sprintf("write:orgs/%s/annotations is unauthorized", annOrgID1),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
var perm influxdb.Permission
if tt.wantErr == nil {
perm = newTestAnnotationsPermission(influxdb.WriteAction, annOrgID1)
svc.EXPECT().
CreateAnnotations(gomock.Any(), *annOrgID1, []influxdb.AnnotationCreate{{}}).
Return(tt.wantRet, nil)
} else {
perm = newTestAnnotationsPermission(influxdb.ReadAction, annOrgID1)
}
ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
got, err := s.CreateAnnotations(ctx, *annOrgID1, []influxdb.AnnotationCreate{{}})
require.Equal(t, tt.wantErr, err)
require.Equal(t, tt.wantRet, got)
})
}
}
func Test_ListAnnotations(t *testing.T) {
t.Parallel()
tests := []struct {
name string
wantRet []influxdb.StoredAnnotation
wantErr error
}{
{
"authorized to list annotations for the specified org",
[]influxdb.StoredAnnotation{},
nil,
},
{
"not authorized to list annotations for the specified org",
nil,
&errors.Error{
Msg: fmt.Sprintf("read:orgs/%s/annotations is unauthorized", annOrgID1),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
var perm influxdb.Permission
if tt.wantErr == nil {
perm = newTestAnnotationsPermission(influxdb.ReadAction, annOrgID1)
svc.EXPECT().
ListAnnotations(gomock.Any(), *annOrgID1, influxdb.AnnotationListFilter{}).
Return(tt.wantRet, nil)
}
ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
got, err := s.ListAnnotations(ctx, *annOrgID1, influxdb.AnnotationListFilter{})
require.Equal(t, tt.wantErr, err)
require.Equal(t, tt.wantRet, got)
})
}
}
func Test_GetAnnotation(t *testing.T) {
t.Parallel()
tests := []struct {
name string
permissionOrg *platform.ID
wantRet *influxdb.StoredAnnotation
wantErr error
}{
{
"authorized to access annotation by id",
annOrgID1,
&influxdb.StoredAnnotation{
ID: *rID,
OrgID: *annOrgID1,
},
nil,
},
{
"not authorized to access annotation by id",
annOrgID2,
nil,
&errors.Error{
Msg: fmt.Sprintf("read:orgs/%s/annotations/%s is unauthorized", annOrgID1, rID),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
svc.EXPECT().
GetAnnotation(gomock.Any(), *rID).
Return(&influxdb.StoredAnnotation{
ID: *rID,
OrgID: *annOrgID1,
}, nil)
perm := newTestAnnotationsPermission(influxdb.ReadAction, tt.permissionOrg)
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
got, err := s.GetAnnotation(ctx, *rID)
require.Equal(t, tt.wantErr, err)
require.Equal(t, tt.wantRet, got)
})
}
}
func Test_DeleteAnnotations(t *testing.T) {
t.Parallel()
tests := []struct {
name string
wantErr error
}{
{
"authorized to delete annotations with the specified org",
nil,
},
{
"not authorized to delete annotations with the specified org",
&errors.Error{
Msg: fmt.Sprintf("write:orgs/%s/annotations is unauthorized", annOrgID1),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
var perm influxdb.Permission
if tt.wantErr == nil {
perm = newTestAnnotationsPermission(influxdb.WriteAction, annOrgID1)
svc.EXPECT().
DeleteAnnotations(gomock.Any(), *annOrgID1, influxdb.AnnotationDeleteFilter{}).
Return(nil)
} else {
perm = newTestAnnotationsPermission(influxdb.ReadAction, annOrgID1)
}
ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
err := s.DeleteAnnotations(ctx, *annOrgID1, influxdb.AnnotationDeleteFilter{})
require.Equal(t, tt.wantErr, err)
})
}
}
func Test_DeleteAnnotation(t *testing.T) {
t.Parallel()
tests := []struct {
name string
permissionOrg *platform.ID
wantErr error
}{
{
"authorized to delete annotation by id",
annOrgID1,
nil,
},
{
"not authorized to delete annotation by id",
annOrgID2,
&errors.Error{
Msg: fmt.Sprintf("write:orgs/%s/annotations/%s is unauthorized", annOrgID1, rID),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
svc.EXPECT().
GetAnnotation(gomock.Any(), *rID).
Return(&influxdb.StoredAnnotation{
ID: *rID,
OrgID: *annOrgID1,
}, nil)
perm := newTestAnnotationsPermission(influxdb.WriteAction, tt.permissionOrg)
if tt.wantErr == nil {
svc.EXPECT().
DeleteAnnotation(gomock.Any(), *rID).
Return(nil)
}
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
err := s.DeleteAnnotation(ctx, *rID)
require.Equal(t, tt.wantErr, err)
})
}
}
func Test_UpdateAnnotation(t *testing.T) {
t.Parallel()
tests := []struct {
name string
permissionOrg *platform.ID
wantRet *influxdb.AnnotationEvent
wantErr error
}{
{
"authorized to update annotation by id",
annOrgID1,
&influxdb.AnnotationEvent{},
nil,
},
{
"not authorized to update annotation by id",
annOrgID2,
nil,
&errors.Error{
Msg: fmt.Sprintf("write:orgs/%s/annotations/%s is unauthorized", annOrgID1, rID),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
svc.EXPECT().
GetAnnotation(gomock.Any(), *rID).
Return(&influxdb.StoredAnnotation{
ID: *rID,
OrgID: *annOrgID1,
}, nil)
perm := newTestAnnotationsPermission(influxdb.WriteAction, tt.permissionOrg)
if tt.wantErr == nil {
svc.EXPECT().
UpdateAnnotation(gomock.Any(), *rID, influxdb.AnnotationCreate{}).
Return(tt.wantRet, nil)
}
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
got, err := s.UpdateAnnotation(ctx, *rID, influxdb.AnnotationCreate{})
require.Equal(t, tt.wantErr, err)
require.Equal(t, tt.wantRet, got)
})
}
}
func Test_ListStreams(t *testing.T) {
t.Parallel()
tests := []struct {
name string
wantRet []influxdb.StoredStream
wantErr error
}{
{
"authorized to list streams for the specified org",
[]influxdb.StoredStream{},
nil,
},
{
"not authorized to list streams for the specified org",
nil,
&errors.Error{
Msg: fmt.Sprintf("read:orgs/%s/annotations is unauthorized", annOrgID1),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
var perm influxdb.Permission
if tt.wantErr == nil {
perm = newTestAnnotationsPermission(influxdb.ReadAction, annOrgID1)
svc.EXPECT().
ListStreams(gomock.Any(), *annOrgID1, influxdb.StreamListFilter{}).
Return(tt.wantRet, nil)
}
ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
got, err := s.ListStreams(ctx, *annOrgID1, influxdb.StreamListFilter{})
require.Equal(t, tt.wantErr, err)
require.Equal(t, tt.wantRet, got)
})
}
}
func Test_GetStream(t *testing.T) {
t.Parallel()
tests := []struct {
name string
permissionOrg *platform.ID
wantRet *influxdb.StoredStream
wantErr error
}{
{
"authorized to access stream by id",
annOrgID1,
&influxdb.StoredStream{
ID: *rID,
OrgID: *annOrgID1,
},
nil,
},
{
"not authorized to access stream by id",
annOrgID2,
nil,
&errors.Error{
Msg: fmt.Sprintf("read:orgs/%s/annotations/%s is unauthorized", annOrgID1, rID),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
svc.EXPECT().
GetStream(gomock.Any(), *rID).
Return(&influxdb.StoredStream{
ID: *rID,
OrgID: *annOrgID1,
}, nil)
perm := newTestAnnotationsPermission(influxdb.ReadAction, tt.permissionOrg)
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
got, err := s.GetStream(ctx, *rID)
require.Equal(t, tt.wantErr, err)
require.Equal(t, tt.wantRet, got)
})
}
}
func Test_CreateOrUpdateStream(t *testing.T) {
t.Parallel()
var (
testStreamName = "test stream"
testStream = influxdb.Stream{
Name: testStreamName,
}
)
t.Run("updating a stream", func(t *testing.T) {
tests := []struct {
name string
permissionOrg *platform.ID
existingStreams []influxdb.StoredStream
getStreamRet *influxdb.StoredStream
wantRet *influxdb.ReadStream
wantErr error
}{
{
"authorized to update an existing stream",
annOrgID1,
[]influxdb.StoredStream{{ID: *rID, OrgID: *annOrgID1}},
&influxdb.StoredStream{ID: *rID, OrgID: *annOrgID1},
&influxdb.ReadStream{ID: *rID},
nil,
},
{
"not authorized to update an existing stream",
annOrgID2,
[]influxdb.StoredStream{{ID: *rID, OrgID: *annOrgID1}},
&influxdb.StoredStream{ID: *rID, OrgID: *annOrgID1},
nil,
&errors.Error{
Msg: fmt.Sprintf("write:orgs/%s/annotations/%s is unauthorized", annOrgID1, rID),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
svc.EXPECT().
ListStreams(gomock.Any(), *tt.permissionOrg, influxdb.StreamListFilter{
StreamIncludes: []string{testStreamName},
}).
Return(tt.existingStreams, nil)
svc.EXPECT().
GetStream(gomock.Any(), tt.existingStreams[0].ID).
Return(tt.getStreamRet, nil)
if tt.wantErr == nil {
svc.EXPECT().
UpdateStream(gomock.Any(), tt.existingStreams[0].ID, testStream).
Return(tt.wantRet, tt.wantErr)
}
perm := newTestAnnotationsPermission(influxdb.WriteAction, tt.permissionOrg)
ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
got, err := s.CreateOrUpdateStream(ctx, *tt.permissionOrg, testStream)
require.Equal(t, tt.wantErr, err)
require.Equal(t, tt.wantRet, got)
})
}
})
t.Run("creating a stream", func(t *testing.T) {
tests := []struct {
name string
existingStreams []influxdb.StoredStream
wantRet *influxdb.ReadStream
wantErr error
}{
{
"authorized to create a stream with the specified org",
[]influxdb.StoredStream{},
&influxdb.ReadStream{},
nil,
},
{
"not authorized to create a stream with the specified org",
[]influxdb.StoredStream{},
nil,
&errors.Error{
Msg: fmt.Sprintf("write:orgs/%s/annotations is unauthorized", annOrgID1),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
svc.EXPECT().
ListStreams(gomock.Any(), *annOrgID1, influxdb.StreamListFilter{
StreamIncludes: []string{testStreamName},
}).
Return(tt.existingStreams, nil)
var perm influxdb.Permission
if tt.wantErr == nil {
perm = newTestAnnotationsPermission(influxdb.WriteAction, annOrgID1)
svc.EXPECT().
CreateOrUpdateStream(gomock.Any(), *annOrgID1, testStream).
Return(tt.wantRet, nil)
} else {
perm = newTestAnnotationsPermission(influxdb.ReadAction, annOrgID1)
}
ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
got, err := s.CreateOrUpdateStream(ctx, *annOrgID1, testStream)
require.Equal(t, tt.wantErr, err)
require.Equal(t, tt.wantRet, got)
})
}
})
t.Run("stream list longer than 1 returns a server error", func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
svc.EXPECT().
ListStreams(gomock.Any(), *annOrgID1, influxdb.StreamListFilter{
StreamIncludes: []string{testStreamName},
}).
Return([]influxdb.StoredStream{{Name: testStreamName}, {Name: testStreamName}}, nil)
wantErr := &errors.Error{
Code: errors.EInternal,
Msg: fmt.Sprintf("more than one stream named %q for org %q", testStreamName, annOrgID1),
}
got, err := s.CreateOrUpdateStream(context.Background(), *annOrgID1, testStream)
require.Nil(t, got)
require.Equal(t, err, wantErr)
})
}
func Test_UpdateStream(t *testing.T) {
t.Parallel()
tests := []struct {
name string
permissionOrg *platform.ID
wantRet *influxdb.ReadStream
wantErr error
}{
{
"authorized to update stream by id",
annOrgID1,
&influxdb.ReadStream{},
nil,
},
{
"not authorized to update stream by id",
annOrgID2,
nil,
&errors.Error{
Msg: fmt.Sprintf("write:orgs/%s/annotations/%s is unauthorized", annOrgID1, rID),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
svc.EXPECT().
GetStream(gomock.Any(), *rID).
Return(&influxdb.StoredStream{
ID: *rID,
OrgID: *annOrgID1,
}, nil)
perm := newTestAnnotationsPermission(influxdb.WriteAction, tt.permissionOrg)
if tt.wantErr == nil {
svc.EXPECT().
UpdateStream(gomock.Any(), *rID, influxdb.Stream{}).
Return(tt.wantRet, nil)
}
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
got, err := s.UpdateStream(ctx, *rID, influxdb.Stream{})
require.Equal(t, tt.wantErr, err)
require.Equal(t, tt.wantRet, got)
})
}
}
func Test_DeleteStreams(t *testing.T) {
t.Parallel()
tests := []struct {
name string
wantErr error
}{
{
"authorized to delete streams with the specified org",
nil,
},
{
"not authorized to delete streams with the specified org",
&errors.Error{
Msg: fmt.Sprintf("write:orgs/%s/annotations is unauthorized", annOrgID1),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
var perm influxdb.Permission
if tt.wantErr == nil {
perm = newTestAnnotationsPermission(influxdb.WriteAction, annOrgID1)
svc.EXPECT().
DeleteStreams(gomock.Any(), *annOrgID1, influxdb.BasicStream{}).
Return(nil)
} else {
perm = newTestAnnotationsPermission(influxdb.ReadAction, annOrgID1)
}
ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
err := s.DeleteStreams(ctx, *annOrgID1, influxdb.BasicStream{})
require.Equal(t, tt.wantErr, err)
})
}
}
func Test_DeleteStreamByID(t *testing.T) {
t.Parallel()
tests := []struct {
name string
permissionOrg *platform.ID
wantErr error
}{
{
"authorized to delete stream by id",
annOrgID1,
nil,
},
{
"not authorized to delete stream by id",
annOrgID2,
&errors.Error{
Msg: fmt.Sprintf("write:orgs/%s/annotations/%s is unauthorized", annOrgID1, rID),
Code: errors.EUnauthorized,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrlr := gomock.NewController(t)
svc := mock.NewMockAnnotationService(ctrlr)
s := authorizer.NewAnnotationService(svc)
svc.EXPECT().
GetStream(gomock.Any(), *rID).
Return(&influxdb.StoredStream{
ID: *rID,
OrgID: *annOrgID1,
}, nil)
perm := newTestAnnotationsPermission(influxdb.WriteAction, tt.permissionOrg)
if tt.wantErr == nil {
svc.EXPECT().
DeleteStreamByID(gomock.Any(), *rID).
Return(nil)
}
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
err := s.DeleteStreamByID(ctx, *rID)
require.Equal(t, tt.wantErr, err)
})
}
}
func newTestAnnotationsPermission(action influxdb.Action, orgID *platform.ID) influxdb.Permission {
return influxdb.Permission{
Action: action,
Resource: influxdb.Resource{
Type: influxdb.AnnotationsResourceType,
OrgID: orgID,
},
}
}

View File

@ -92,6 +92,42 @@ func AuthorizeFindDashboards(ctx context.Context, rs []*influxdb.Dashboard) ([]*
return rrs, len(rrs), nil
}
// AuthorizeFindAnnotations takes the given items and returns only the ones that the user is authorized to read.
func AuthorizeFindAnnotations(ctx context.Context, rs []influxdb.StoredAnnotation) ([]influxdb.StoredAnnotation, int, error) {
// This filters without allocating
// https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
rrs := rs[:0]
for _, r := range rs {
_, _, err := AuthorizeRead(ctx, influxdb.AnnotationsResourceType, r.ID, r.OrgID)
if err != nil && errors.ErrorCode(err) != errors.EUnauthorized {
return nil, 0, err
}
if errors.ErrorCode(err) == errors.EUnauthorized {
continue
}
rrs = append(rrs, r)
}
return rrs, len(rrs), nil
}
// AuthorizeFindStreams takes the given items and returns only the ones that the user is authorized to read.
func AuthorizeFindStreams(ctx context.Context, rs []influxdb.StoredStream) ([]influxdb.StoredStream, int, error) {
// This filters without allocating
// https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
rrs := rs[:0]
for _, r := range rs {
_, _, err := AuthorizeRead(ctx, influxdb.AnnotationsResourceType, r.ID, r.OrgID)
if err != nil && errors.ErrorCode(err) != errors.EUnauthorized {
return nil, 0, err
}
if errors.ErrorCode(err) == errors.EUnauthorized {
continue
}
rrs = append(rrs, r)
}
return rrs, len(rrs), nil
}
// AuthorizeFindOrganizations takes the given items and returns only the ones that the user is authorized to read.
func AuthorizeFindOrganizations(ctx context.Context, rs []*influxdb.Organization) ([]*influxdb.Organization, int, error) {
// This filters without allocating

View File

@ -61,7 +61,7 @@ func Test_GetNotebook(t *testing.T) {
GetNotebook(gomock.Any(), *nbID).
Return(newTestNotebook(*orgID1), nil)
perm := newTestPermission(influxdb.ReadAction, tt.permissionOrg)
perm := newTestNotebooksPermission(influxdb.ReadAction, tt.permissionOrg)
ctx := context.Background()
ctx = influxdbcontext.SetAuthorizer(ctx, mock.NewMockAuthorizer(false, []influxdb.Permission{perm}))
@ -107,7 +107,7 @@ func Test_CreateNotebook(t *testing.T) {
svc := mock.NewMockNotebookService(ctrlr)
s := authorizer.NewNotebookService(svc)
perm := newTestPermission(influxdb.WriteAction, tt.permissionOrg)
perm := newTestNotebooksPermission(influxdb.WriteAction, tt.permissionOrg)
nb := newTestReqBody(*tt.notebookOrg)
if tt.wantErr == nil {
@ -163,7 +163,7 @@ func Test_UpdateNotebook(t *testing.T) {
GetNotebook(gomock.Any(), *nbID).
Return(newTestNotebook(*tt.notebookOrg), nil)
perm := newTestPermission(influxdb.WriteAction, tt.permissionOrg)
perm := newTestNotebooksPermission(influxdb.WriteAction, tt.permissionOrg)
nb := newTestReqBody(*tt.notebookOrg)
if tt.wantErr == nil {
@ -216,7 +216,7 @@ func Test_DeleteNotebook(t *testing.T) {
GetNotebook(gomock.Any(), *nbID).
Return(newTestNotebook(*tt.notebookOrg), nil)
perm := newTestPermission(influxdb.WriteAction, tt.permissionOrg)
perm := newTestNotebooksPermission(influxdb.WriteAction, tt.permissionOrg)
if tt.wantErr == nil {
svc.EXPECT().
@ -266,7 +266,7 @@ func Test_ListNotebooks(t *testing.T) {
svc := mock.NewMockNotebookService(ctrlr)
s := authorizer.NewNotebookService(svc)
perm := newTestPermission(influxdb.ReadAction, tt.permissionOrg)
perm := newTestNotebooksPermission(influxdb.ReadAction, tt.permissionOrg)
filter := influxdb.NotebookListFilter{OrgID: *tt.notebookOrg}
if tt.wantErr == nil {
@ -304,7 +304,7 @@ func newTestReqBody(orgID platform.ID) *influxdb.NotebookReqBody {
}
}
func newTestPermission(action influxdb.Action, orgID *platform.ID) influxdb.Permission {
func newTestNotebooksPermission(action influxdb.Action, orgID *platform.ID) influxdb.Permission {
return influxdb.Permission{
Action: action,
Resource: influxdb.Resource{

View File

@ -137,6 +137,8 @@ const (
DBRPResourceType = ResourceType("dbrp") // 17
// NotebooksResourceType gives permission to one or more notebooks.
NotebooksResourceType = ResourceType("notebooks") // 18
// AnnotationsResourceType gives permission to one or more annotations.
AnnotationsResourceType = ResourceType("annotations") // 19
)
// AllResourceTypes is the list of all known resource types.
@ -160,6 +162,7 @@ var AllResourceTypes = []ResourceType{
ChecksResourceType, // 16
DBRPResourceType, // 17
NotebooksResourceType, // 18
AnnotationsResourceType, // 19
// NOTE: when modifying this list, please update the swagger for components.schemas.Permission resource enum.
}
@ -179,6 +182,7 @@ var OrgResourceTypes = []ResourceType{
ChecksResourceType, // 16
DBRPResourceType, // 17
NotebooksResourceType, // 18
AnnotationsResourceType, // 19
}
// Valid checks if the resource type is a member of the ResourceType enum.
@ -208,6 +212,7 @@ func (t ResourceType) Valid() (err error) {
case ChecksResourceType: // 16
case DBRPResourceType: // 17
case NotebooksResourceType: // 18
case AnnotationsResourceType: // 19
default:
err = ErrInvalidResourceType
}

View File

@ -325,7 +325,8 @@ func TestPermissionAllResources_Valid(t *testing.T) {
platform.BucketsResourceType,
platform.DashboardsResourceType,
platform.SourcesResourceType,
platform.DashboardsResourceType,
platform.NotebooksResourceType,
platform.AnnotationsResourceType,
}
for _, rt := range resources {

View File

@ -6,37 +6,36 @@ package mock
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
influxdb "github.com/influxdata/influxdb/v2"
platform "github.com/influxdata/influxdb/v2/kit/platform"
reflect "reflect"
)
// MockAnnotationService is a mock of AnnotationService interface.
// MockAnnotationService is a mock of AnnotationService interface
type MockAnnotationService struct {
ctrl *gomock.Controller
recorder *MockAnnotationServiceMockRecorder
}
// MockAnnotationServiceMockRecorder is the mock recorder for MockAnnotationService.
// MockAnnotationServiceMockRecorder is the mock recorder for MockAnnotationService
type MockAnnotationServiceMockRecorder struct {
mock *MockAnnotationService
}
// NewMockAnnotationService creates a new mock instance.
// NewMockAnnotationService creates a new mock instance
func NewMockAnnotationService(ctrl *gomock.Controller) *MockAnnotationService {
mock := &MockAnnotationService{ctrl: ctrl}
mock.recorder = &MockAnnotationServiceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockAnnotationService) EXPECT() *MockAnnotationServiceMockRecorder {
return m.recorder
}
// CreateAnnotations mocks base method.
// CreateAnnotations mocks base method
func (m *MockAnnotationService) CreateAnnotations(ctx context.Context, orgID platform.ID, create []influxdb.AnnotationCreate) ([]influxdb.AnnotationEvent, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateAnnotations", ctx, orgID, create)
@ -45,42 +44,43 @@ func (m *MockAnnotationService) CreateAnnotations(ctx context.Context, orgID pla
return ret0, ret1
}
// CreateAnnotations indicates an expected call of CreateAnnotations.
// CreateAnnotations indicates an expected call of CreateAnnotations
func (mr *MockAnnotationServiceMockRecorder) CreateAnnotations(ctx, orgID, create interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAnnotations", reflect.TypeOf((*MockAnnotationService)(nil).CreateAnnotations), ctx, orgID, create)
}
// CreateOrUpdateStream mocks base method.
func (m *MockAnnotationService) CreateOrUpdateStream(ctx context.Context, stream influxdb.Stream) (*influxdb.ReadStream, error) {
// ListAnnotations mocks base method
func (m *MockAnnotationService) ListAnnotations(ctx context.Context, orgID platform.ID, filter influxdb.AnnotationListFilter) ([]influxdb.StoredAnnotation, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdateStream", ctx, stream)
ret0, _ := ret[0].(*influxdb.ReadStream)
ret := m.ctrl.Call(m, "ListAnnotations", ctx, orgID, filter)
ret0, _ := ret[0].([]influxdb.StoredAnnotation)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateOrUpdateStream indicates an expected call of CreateOrUpdateStream.
func (mr *MockAnnotationServiceMockRecorder) CreateOrUpdateStream(ctx, stream interface{}) *gomock.Call {
// ListAnnotations indicates an expected call of ListAnnotations
func (mr *MockAnnotationServiceMockRecorder) ListAnnotations(ctx, orgID, filter interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateStream", reflect.TypeOf((*MockAnnotationService)(nil).CreateOrUpdateStream), ctx, stream)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAnnotations", reflect.TypeOf((*MockAnnotationService)(nil).ListAnnotations), ctx, orgID, filter)
}
// DeleteAnnotation mocks base method.
func (m *MockAnnotationService) DeleteAnnotation(ctx context.Context, id platform.ID) error {
// GetAnnotation mocks base method
func (m *MockAnnotationService) GetAnnotation(ctx context.Context, id platform.ID) (*influxdb.StoredAnnotation, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAnnotation", ctx, id)
ret0, _ := ret[0].(error)
return ret0
ret := m.ctrl.Call(m, "GetAnnotation", ctx, id)
ret0, _ := ret[0].(*influxdb.StoredAnnotation)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DeleteAnnotation indicates an expected call of DeleteAnnotation.
func (mr *MockAnnotationServiceMockRecorder) DeleteAnnotation(ctx, id interface{}) *gomock.Call {
// GetAnnotation indicates an expected call of GetAnnotation
func (mr *MockAnnotationServiceMockRecorder) GetAnnotation(ctx, id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAnnotation", reflect.TypeOf((*MockAnnotationService)(nil).DeleteAnnotation), ctx, id)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAnnotation", reflect.TypeOf((*MockAnnotationService)(nil).GetAnnotation), ctx, id)
}
// DeleteAnnotations mocks base method.
// DeleteAnnotations mocks base method
func (m *MockAnnotationService) DeleteAnnotations(ctx context.Context, orgID platform.ID, delete influxdb.AnnotationDeleteFilter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAnnotations", ctx, orgID, delete)
@ -88,86 +88,27 @@ func (m *MockAnnotationService) DeleteAnnotations(ctx context.Context, orgID pla
return ret0
}
// DeleteAnnotations indicates an expected call of DeleteAnnotations.
// DeleteAnnotations indicates an expected call of DeleteAnnotations
func (mr *MockAnnotationServiceMockRecorder) DeleteAnnotations(ctx, orgID, delete interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAnnotations", reflect.TypeOf((*MockAnnotationService)(nil).DeleteAnnotations), ctx, orgID, delete)
}
// DeleteStreamByID mocks base method.
func (m *MockAnnotationService) DeleteStreamByID(ctx context.Context, id platform.ID) error {
// DeleteAnnotation mocks base method
func (m *MockAnnotationService) DeleteAnnotation(ctx context.Context, id platform.ID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteStreamByID", ctx, id)
ret := m.ctrl.Call(m, "DeleteAnnotation", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteStreamByID indicates an expected call of DeleteStreamByID.
func (mr *MockAnnotationServiceMockRecorder) DeleteStreamByID(ctx, id interface{}) *gomock.Call {
// DeleteAnnotation indicates an expected call of DeleteAnnotation
func (mr *MockAnnotationServiceMockRecorder) DeleteAnnotation(ctx, id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteStreamByID", reflect.TypeOf((*MockAnnotationService)(nil).DeleteStreamByID), ctx, id)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAnnotation", reflect.TypeOf((*MockAnnotationService)(nil).DeleteAnnotation), ctx, id)
}
// DeleteStreams mocks base method.
func (m *MockAnnotationService) DeleteStreams(ctx context.Context, delete influxdb.BasicStream) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteStreams", ctx, delete)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteStreams indicates an expected call of DeleteStreams.
func (mr *MockAnnotationServiceMockRecorder) DeleteStreams(ctx, delete interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteStreams", reflect.TypeOf((*MockAnnotationService)(nil).DeleteStreams), ctx, delete)
}
// GetAnnotation mocks base method.
func (m *MockAnnotationService) GetAnnotation(ctx context.Context, id platform.ID) (*influxdb.AnnotationEvent, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAnnotation", ctx, id)
ret0, _ := ret[0].(*influxdb.AnnotationEvent)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAnnotation indicates an expected call of GetAnnotation.
func (mr *MockAnnotationServiceMockRecorder) GetAnnotation(ctx, id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAnnotation", reflect.TypeOf((*MockAnnotationService)(nil).GetAnnotation), ctx, id)
}
// ListAnnotations mocks base method.
func (m *MockAnnotationService) ListAnnotations(ctx context.Context, orgID platform.ID, filter influxdb.AnnotationListFilter) (influxdb.ReadAnnotations, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListAnnotations", ctx, orgID, filter)
ret0, _ := ret[0].(influxdb.ReadAnnotations)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListAnnotations indicates an expected call of ListAnnotations.
func (mr *MockAnnotationServiceMockRecorder) ListAnnotations(ctx, orgID, filter interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAnnotations", reflect.TypeOf((*MockAnnotationService)(nil).ListAnnotations), ctx, orgID, filter)
}
// ListStreams mocks base method.
func (m *MockAnnotationService) ListStreams(ctx context.Context, orgID platform.ID, filter influxdb.StreamListFilter) ([]influxdb.ReadStream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListStreams", ctx, orgID, filter)
ret0, _ := ret[0].([]influxdb.ReadStream)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListStreams indicates an expected call of ListStreams.
func (mr *MockAnnotationServiceMockRecorder) ListStreams(ctx, orgID, filter interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListStreams", reflect.TypeOf((*MockAnnotationService)(nil).ListStreams), ctx, orgID, filter)
}
// UpdateAnnotation mocks base method.
// UpdateAnnotation mocks base method
func (m *MockAnnotationService) UpdateAnnotation(ctx context.Context, id platform.ID, update influxdb.AnnotationCreate) (*influxdb.AnnotationEvent, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateAnnotation", ctx, id, update)
@ -176,13 +117,58 @@ func (m *MockAnnotationService) UpdateAnnotation(ctx context.Context, id platfor
return ret0, ret1
}
// UpdateAnnotation indicates an expected call of UpdateAnnotation.
// UpdateAnnotation indicates an expected call of UpdateAnnotation
func (mr *MockAnnotationServiceMockRecorder) UpdateAnnotation(ctx, id, update interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAnnotation", reflect.TypeOf((*MockAnnotationService)(nil).UpdateAnnotation), ctx, id, update)
}
// UpdateStream mocks base method.
// ListStreams mocks base method
func (m *MockAnnotationService) ListStreams(ctx context.Context, orgID platform.ID, filter influxdb.StreamListFilter) ([]influxdb.StoredStream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListStreams", ctx, orgID, filter)
ret0, _ := ret[0].([]influxdb.StoredStream)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListStreams indicates an expected call of ListStreams
func (mr *MockAnnotationServiceMockRecorder) ListStreams(ctx, orgID, filter interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListStreams", reflect.TypeOf((*MockAnnotationService)(nil).ListStreams), ctx, orgID, filter)
}
// CreateOrUpdateStream mocks base method
func (m *MockAnnotationService) CreateOrUpdateStream(ctx context.Context, orgID platform.ID, stream influxdb.Stream) (*influxdb.ReadStream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdateStream", ctx, orgID, stream)
ret0, _ := ret[0].(*influxdb.ReadStream)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateOrUpdateStream indicates an expected call of CreateOrUpdateStream
func (mr *MockAnnotationServiceMockRecorder) CreateOrUpdateStream(ctx, orgID, stream interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateStream", reflect.TypeOf((*MockAnnotationService)(nil).CreateOrUpdateStream), ctx, orgID, stream)
}
// GetStream mocks base method
func (m *MockAnnotationService) GetStream(ctx context.Context, id platform.ID) (*influxdb.StoredStream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStream", ctx, id)
ret0, _ := ret[0].(*influxdb.StoredStream)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetStream indicates an expected call of GetStream
func (mr *MockAnnotationServiceMockRecorder) GetStream(ctx, id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStream", reflect.TypeOf((*MockAnnotationService)(nil).GetStream), ctx, id)
}
// UpdateStream mocks base method
func (m *MockAnnotationService) UpdateStream(ctx context.Context, id platform.ID, stream influxdb.Stream) (*influxdb.ReadStream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateStream", ctx, id, stream)
@ -191,8 +177,36 @@ func (m *MockAnnotationService) UpdateStream(ctx context.Context, id platform.ID
return ret0, ret1
}
// UpdateStream indicates an expected call of UpdateStream.
// UpdateStream indicates an expected call of UpdateStream
func (mr *MockAnnotationServiceMockRecorder) UpdateStream(ctx, id, stream interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStream", reflect.TypeOf((*MockAnnotationService)(nil).UpdateStream), ctx, id, stream)
}
// DeleteStreams mocks base method
func (m *MockAnnotationService) DeleteStreams(ctx context.Context, orgID platform.ID, delete influxdb.BasicStream) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteStreams", ctx, orgID, delete)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteStreams indicates an expected call of DeleteStreams
func (mr *MockAnnotationServiceMockRecorder) DeleteStreams(ctx, orgID, delete interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteStreams", reflect.TypeOf((*MockAnnotationService)(nil).DeleteStreams), ctx, orgID, delete)
}
// DeleteStreamByID mocks base method
func (m *MockAnnotationService) DeleteStreamByID(ctx context.Context, id platform.ID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteStreamByID", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteStreamByID indicates an expected call of DeleteStreamByID
func (mr *MockAnnotationServiceMockRecorder) DeleteStreamByID(ctx, id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteStreamByID", reflect.TypeOf((*MockAnnotationService)(nil).DeleteStreamByID), ctx, id)
}

View File

@ -162,6 +162,8 @@ func TestOnboardAuth(t *testing.T) {
{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.DBRPResourceType}},
{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.NotebooksResourceType}},
{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.NotebooksResourceType}},
{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.AnnotationsResourceType}},
{Action: influxdb.WriteAction, Resource: influxdb.Resource{OrgID: &onboard.Org.ID, Type: influxdb.AnnotationsResourceType}},
{Action: influxdb.ReadAction, Resource: influxdb.Resource{ID: &onboard.User.ID, Type: influxdb.UsersResourceType}},
{Action: influxdb.WriteAction, Resource: influxdb.Resource{ID: &onboard.User.ID, Type: influxdb.UsersResourceType}},
}

View File

@ -157,6 +157,7 @@ func TestFindPermissionsFromUser(t *testing.T) {
influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &orgID, Type: influxdb.ChecksResourceType}},
influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &orgID, Type: influxdb.DBRPResourceType}},
influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &orgID, Type: influxdb.NotebooksResourceType}},
influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{OrgID: &orgID, Type: influxdb.AnnotationsResourceType}},
influxdb.Permission{Action: influxdb.ReadAction, Resource: influxdb.Resource{Type: influxdb.UsersResourceType, ID: &u.ID}},
influxdb.Permission{Action: influxdb.WriteAction, Resource: influxdb.Resource{Type: influxdb.UsersResourceType, ID: &u.ID}},
}