diff --git a/integrations/server_test.go b/integrations/server_test.go index 596fc9fc6..c582b075c 100644 --- a/integrations/server_test.go +++ b/integrations/server_test.go @@ -1893,6 +1893,74 @@ func TestServer(t *testing.T) { "code": 401, "message": "user cannot modify their own SuperAdmin status" } +`, + }, + }, + { + name: "GET /organization/default/users", + subName: "Organization not set explicitly on principal", + fields: fields{ + Config: &chronograf.Config{ + Auth: chronograf.AuthConfig{ + SuperAdminNewUsers: false, + }, + }, + Organizations: []chronograf.Organization{}, + Users: []chronograf.User{ + { + ID: 1, // This is artificial, but should be reflective of the users actual ID + Name: "billibob", + Provider: "github", + Scheme: "oauth2", + SuperAdmin: true, + Roles: []chronograf.Role{ + { + Name: "admin", + Organization: "default", + }, + }, + }, + }, + }, + args: args{ + server: &server.Server{ + GithubClientID: "not empty", + GithubClientSecret: "not empty", + }, + method: "GET", + path: "/chronograf/v1/organizations/default/users", + principal: oauth2.Principal{ + Organization: "", + Subject: "billibob", + Issuer: "github", + }, + }, + wants: wants{ + statusCode: 200, + body: ` +{ + "links": { + "self": "/chronograf/v1/organizations/default/users" + }, + "users": [ + { + "links": { + "self": "/chronograf/v1/organizations/default/users/1" + }, + "id": "1", + "name": "billibob", + "provider": "github", + "scheme": "oauth2", + "superAdmin": true, + "roles": [ + { + "name": "admin", + "organization": "default" + } + ] + } + ] +} `, }, }, diff --git a/server/middle.go b/server/middle.go index c8649b22a..7d2ec52c3 100644 --- a/server/middle.go +++ b/server/middle.go @@ -10,6 +10,7 @@ import ( // RouteMatchesPrincipal checks that the organization on context matches the organization // in the route. func RouteMatchesPrincipal( + store DataStore, useAuth bool, logger chronograf.Logger, next http.HandlerFunc, @@ -35,6 +36,16 @@ func RouteMatchesPrincipal( return } + if p.Organization == "" { + defaultOrg, err := store.Organizations(ctx).DefaultOrganization(ctx) + if err != nil { + log.Error("Failed to look up default organization") + Error(w, http.StatusForbidden, "User is not authorized", logger) + return + } + p.Organization = defaultOrg.ID + } + if orgID != p.Organization { log.Error("Route organization does not match the organization on principal") Error(w, http.StatusForbidden, "User is not authorized", logger) diff --git a/server/middle_test.go b/server/middle_test.go index 31ae2d862..a57f56370 100644 --- a/server/middle_test.go +++ b/server/middle_test.go @@ -9,12 +9,14 @@ import ( "github.com/bouk/httprouter" "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/log" + "github.com/influxdata/chronograf/mocks" "github.com/influxdata/chronograf/oauth2" ) func TestRouteMatchesPrincipal(t *testing.T) { type fields struct { - Logger chronograf.Logger + OrganizationsStore chronograf.OrganizationsStore + Logger chronograf.Logger } type args struct { useAuth bool @@ -34,6 +36,13 @@ func TestRouteMatchesPrincipal(t *testing.T) { name: "route matches request params", fields: fields{ Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "default", + }, nil + }, + }, }, args: args{ useAuth: true, @@ -57,6 +66,13 @@ func TestRouteMatchesPrincipal(t *testing.T) { name: "route does not match request params", fields: fields{ Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "default", + }, nil + }, + }, }, args: args{ useAuth: true, @@ -80,6 +96,13 @@ func TestRouteMatchesPrincipal(t *testing.T) { name: "missing principal", fields: fields{ Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "default", + }, nil + }, + }, }, args: args{ useAuth: true, @@ -99,6 +122,13 @@ func TestRouteMatchesPrincipal(t *testing.T) { name: "not using auth", fields: fields{ Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "default", + }, nil + }, + }, }, args: args{ useAuth: false, @@ -122,11 +152,15 @@ func TestRouteMatchesPrincipal(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + store := &mocks.Store{ + OrganizationsStore: tt.fields.OrganizationsStore, + } var matches bool next := func(w http.ResponseWriter, r *http.Request) { matches = true } fn := RouteMatchesPrincipal( + store, tt.args.useAuth, tt.fields.Logger, next, diff --git a/server/mux.go b/server/mux.go index 903cd94f5..1fb2a8f0c 100644 --- a/server/mux.go +++ b/server/mux.go @@ -121,6 +121,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler { ensureOrgMatches := func(next http.HandlerFunc) http.HandlerFunc { return RouteMatchesPrincipal( + service.Store, opts.UseAuth, opts.Logger, next,