diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index bbfd26c94..428d8f296 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -14,7 +14,7 @@ message Source { string MetaURL = 10; // MetaURL is the connection URL for the meta node. string SharedSecret = 11; // SharedSecret signs the optional InfluxDB JWT Authorization string Organization = 12; // Organization is the organization ID that resource belongs to - string Role = 13; // Role is the name of the role that a user must posses to access the resource + string Role = 13; // Role is the name of the miniumum role that a user must possess to access the resource } message Dashboard { diff --git a/chronograf.go b/chronograf.go index 7fe93769a..b540b1d2d 100644 --- a/chronograf.go +++ b/chronograf.go @@ -450,7 +450,7 @@ type Source struct { Default bool `json:"default"` // Default specifies the default source for the application Telegraf string `json:"telegraf"` // Telegraf is the db telegraf is written to. By default it is "telegraf" Organization string `json:"organization"` // Organization is the organization ID that resource belongs to - Role string `json:"role"` // Role is the name of the role that a user must posses to access the resource. + Role string `json:"role"` // Role is the name of the minimum role that a user must possess to access the resource. } // SourcesStore stores connection information for a `TimeSeries` diff --git a/roles/roles.go b/roles/roles.go index b5019a9ca..e0ecbd2c1 100644 --- a/roles/roles.go +++ b/roles/roles.go @@ -46,17 +46,17 @@ var ( Name: MemberRoleName, } - // ViewerRole is the role for a user who can only perform READ operations on Dashboards, Rules, and Sources + // ViewerRole is the role for a user who can only perform READ operations on Dashboards, Rules, Sources, and Servers, ViewerRole = chronograf.Role{ Name: ViewerRoleName, } - // EditorRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, and Sources + // EditorRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, and Servers. EditorRole = chronograf.Role{ Name: EditorRoleName, } - // AdminRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, and Users + // AdminRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, Servers, and Users AdminRole = chronograf.Role{ Name: AdminRoleName, } diff --git a/roles/sources.go b/roles/sources.go index e2b65145d..cc69eddb3 100644 --- a/roles/sources.go +++ b/roles/sources.go @@ -10,7 +10,10 @@ import ( var _ chronograf.SourcesStore = &SourcesStore{} // SourcesStore facade on a SourceStore that filters sources -// by role. +// by minimum role required to access the source. +// +// The role is passed around on the context and set when the +// SourcesStore is instantiated. type SourcesStore struct { store chronograf.SourcesStore role string @@ -110,6 +113,8 @@ func (s *SourcesStore) Update(ctx context.Context, d chronograf.Source) error { return s.store.Update(ctx, d) } +// hasAuthorizedRole checks that the role provided has at least +// the minimum role required. func hasAuthorizedRole(sourceRole, providedRole string) bool { switch sourceRole { case ViewerRoleName: diff --git a/server/auth.go b/server/auth.go index 7cb28342e..79a0677e8 100644 --- a/server/auth.go +++ b/server/auth.go @@ -69,7 +69,6 @@ func AuthorizedUser( return } ctx = context.WithValue(ctx, organizations.ContextKey, fmt.Sprintf("%d", defaultOrg.ID)) - // TODO(desa): remove this in place of actual string value ctx = context.WithValue(ctx, roles.ContextKey, roles.AdminRoleName) r = r.WithContext(ctx) next(w, r) diff --git a/server/stores_test.go b/server/stores_test.go index 728c15280..50f162517 100644 --- a/server/stores_test.go +++ b/server/stores_test.go @@ -33,7 +33,7 @@ func TestStore_SourcesGet(t *testing.T) { wants wants }{ { - name: "Get user as super admin", + name: "Get viewer source as viewer", fields: fields{ SourcesStore: &mocks.SourcesStore{ GetF: func(ctx context.Context, id int) (chronograf.Source, error) { @@ -47,7 +47,6 @@ func TestStore_SourcesGet(t *testing.T) { }, }, args: args{ - superAdmin: true, organization: "0", role: "viewer", }, @@ -61,7 +60,236 @@ func TestStore_SourcesGet(t *testing.T) { }, }, { - name: "Get user as super admin", + name: "Get viewer source as editor", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "editor", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + { + name: "Get viewer source as admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "admin", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + { + name: "Get admin source as viewer", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "viewer", + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get editor source as viewer", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "viewer", + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get editor source as editor", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "editor", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + }, + }, + { + name: "Get editor source as admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "admin", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + }, + }, + { + name: "Get editor source as viewer", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "viewer", + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get admin source as admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "admin", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, + }, + }, + { + name: "Get source as super admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "viewer", + superAdmin: true, + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + { + name: "Get source as super admin - no organization or role", fields: fields{ SourcesStore: &mocks.SourcesStore{ GetF: func(ctx context.Context, id int) (chronograf.Source, error) { @@ -82,7 +310,29 @@ func TestStore_SourcesGet(t *testing.T) { }, }, { - name: "Get user as super admin", + name: "Get source as super admin - no role", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + superAdmin: true, + organization: "0", + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get source as super admin - no organization", fields: fields{ SourcesStore: &mocks.SourcesStore{ GetF: func(ctx context.Context, id int) (chronograf.Source, error) { @@ -97,6 +347,7 @@ func TestStore_SourcesGet(t *testing.T) { }, args: args{ superAdmin: true, + role: "viewer", }, wants: wants{ err: true, diff --git a/server/users.go b/server/users.go index 10a3d99ef..c6cee439d 100644 --- a/server/users.go +++ b/server/users.go @@ -68,7 +68,7 @@ func (r *userRequest) ValidRoles() error { case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName: continue default: - return fmt.Errorf("Unknown role %s. Valid roles are 'member', 'viewer', 'editor', 'admin', and 'superadmin'", r.Name) + return fmt.Errorf("Unknown role %s. Valid roles are 'member', 'viewer', 'editor', and 'admin'", r.Name) } } } diff --git a/server/users_test.go b/server/users_test.go index e1e221b32..ff8cb6e3b 100644 --- a/server/users_test.go +++ b/server/users_test.go @@ -984,7 +984,7 @@ func TestUserRequest_ValidCreate(t *testing.T) { }, }, wantErr: true, - err: fmt.Errorf("Unknown role BilliettaSpecialRole. Valid roles are 'member', 'viewer', 'editor', 'admin', and 'superadmin'"), + err: fmt.Errorf("Unknown role BilliettaSpecialRole. Valid roles are 'member', 'viewer', 'editor', and 'admin'"), }, }