diff --git a/integrations/server_test.go b/integrations/server_test.go index 9e6265ba5..0eb028bd9 100644 --- a/integrations/server_test.go +++ b/integrations/server_test.go @@ -1303,6 +1303,201 @@ func TestServer(t *testing.T) { "code": 401, "message": "user cannot modify their own SuperAdmin status" } +`, + }, + }, + { + name: "GET /me", + subName: "New user hits me for the first time", + fields: fields{ + Config: &chronograf.Config{ + Auth: chronograf.AuthConfig{ + SuperAdminNewUsers: false, + }, + }, + Organizations: []chronograf.Organization{ + { + ID: "1", + Name: "Sweet", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.EditorRoleName, + }, + chronograf.Mapping{ + Provider: "github", + Scheme: chronograf.MappingWildcard, + Group: "influxdata", + GrantedRole: roles.AdminRoleName, + }, + chronograf.Mapping{ + Provider: "github", + Scheme: chronograf.MappingWildcard, + Group: "mimi", + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + { + ID: "2", + Name: "What", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.MemberRoleName, + }, + chronograf.Mapping{ + Provider: "github", + Scheme: chronograf.MappingWildcard, + Group: "mimi", + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + { + ID: "3", + Name: "Okay", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{}, + }, + }, + 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/me", + principal: oauth2.Principal{ + Subject: "billietta", + Issuer: "github", + Group: "influxdata,idk,mimi", + }, + }, + wants: wants{ + statusCode: 200, + body: ` +{ + "id": "2", + "name": "billietta", + "roles": [ + { + "name": "admin", + "organization": "1" + }, + { + "name": "viewer", + "organization": "2" + }, + { + "name": "member", + "organization": "default" + } + ], + "provider": "github", + "scheme": "oauth2", + "links": { + "self": "/chronograf/v1/users/2" + }, + "organizations": [ + { + "id": "1", + "name": "Sweet", + "defaultRole": "viewer", + "public": false, + "mappings": [ + { + "provider": "*", + "scheme": "*", + "group": "*", + "grantedRole": "editor" + }, + { + "provider": "github", + "scheme": "*", + "group": "influxdata", + "grantedRole": "admin" + }, + { + "provider": "github", + "scheme": "*", + "group": "mimi", + "grantedRole": "viewer" + } + ] + }, + { + "id": "2", + "name": "What", + "defaultRole": "viewer", + "public": false, + "mappings": [ + { + "provider": "*", + "scheme": "*", + "group": "*", + "grantedRole": "member" + }, + { + "provider": "github", + "scheme": "*", + "group": "mimi", + "grantedRole": "viewer" + } + ] + }, + { + "id": "default", + "name": "Default", + "defaultRole": "member", + "public": true, + "mappings": [ + { + "provider": "*", + "scheme": "*", + "group": "*", + "grantedRole": "member" + } + ] + } + ], + "currentOrganization": { + "id": "default", + "name": "Default", + "defaultRole": "member", + "public": true, + "mappings": [ + { + "provider": "*", + "scheme": "*", + "group": "*", + "grantedRole": "member" + } + ] + } +} `, }, }, diff --git a/server/mapping.go b/server/mapping.go index e7534e6af..07b675188 100644 --- a/server/mapping.go +++ b/server/mapping.go @@ -8,7 +8,7 @@ import ( "github.com/influxdata/chronograf/roles" ) -func MappedRole(o *chronograf.Organization, p oauth2.Principal) *chronograf.Role { +func MappedRole(o chronograf.Organization, p oauth2.Principal) *chronograf.Role { roles := []*chronograf.Role{} for _, mapping := range o.Mappings { role := applyMapping(mapping, p) diff --git a/server/mapping_test.go b/server/mapping_test.go index 199e8ef20..084b0e77a 100644 --- a/server/mapping_test.go +++ b/server/mapping_test.go @@ -12,7 +12,7 @@ import ( func TestMappedRole(t *testing.T) { type args struct { - org *chronograf.Organization + org chronograf.Organization principal oauth2.Principal } type wants struct { @@ -27,7 +27,7 @@ func TestMappedRole(t *testing.T) { { name: "single mapping all wildcards", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -50,7 +50,7 @@ func TestMappedRole(t *testing.T) { { name: "two mapping all wildcards", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -79,7 +79,7 @@ func TestMappedRole(t *testing.T) { { name: "two mapping all wildcards, different order", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -113,7 +113,7 @@ func TestMappedRole(t *testing.T) { Issuer: "google", Group: "influxdata.com", }, - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -142,7 +142,7 @@ func TestMappedRole(t *testing.T) { { name: "different two mapping all wildcards", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -171,7 +171,7 @@ func TestMappedRole(t *testing.T) { { name: "different two mapping all wildcards, different ordering", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -200,7 +200,7 @@ func TestMappedRole(t *testing.T) { { name: "three mapping all wildcards", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -240,7 +240,7 @@ func TestMappedRole(t *testing.T) { Issuer: "google", Group: "influxdata.com", }, - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -280,7 +280,7 @@ func TestMappedRole(t *testing.T) { Issuer: "google", Group: "influxdata.com", }, - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -320,7 +320,7 @@ func TestMappedRole(t *testing.T) { Issuer: "google", Group: "influxdata.com", }, - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -345,7 +345,7 @@ func TestMappedRole(t *testing.T) { Issuer: "github", Group: "influxdata,another,mimi", }, - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ diff --git a/server/me.go b/server/me.go index d2a4bfb1c..3b53b9dc8 100644 --- a/server/me.go +++ b/server/me.go @@ -284,17 +284,25 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { // support OAuth2. This hard-coding should be removed whenever we add // support for other authentication schemes. Scheme: scheme, - Roles: []chronograf.Role{ - { - Name: defaultOrg.DefaultRole, - // This is the ID of the default organization - Organization: defaultOrg.ID, - }, - }, // TODO(desa): this needs a better name SuperAdmin: s.newUsersAreSuperAdmin(), } + allOrgs, err := s.Store.Organizations(serverCtx).All(serverCtx) + if err != nil { + Error(w, http.StatusInternalServerError, err.Error(), s.Logger) + return + } + roles := []chronograf.Role{} + for _, org := range allOrgs { + role := MappedRole(org, p) + if role != nil { + roles = append(roles, *role) + } + } + + user.Roles = roles + newUser, err := s.Store.Users(serverCtx).Add(serverCtx, user) if err != nil { msg := fmt.Errorf("error storing user %s: %v", user.Name, err) diff --git a/server/me_test.go b/server/me_test.go index 35df19ba2..f52a9725c 100644 --- a/server/me_test.go +++ b/server/me_test.go @@ -413,6 +413,24 @@ func TestService_Me(t *testing.T) { Mappings: []chronograf.Mapping{}, }, nil }, + AllF: func(ctx context.Context) ([]chronograf.Organization, error) { + return []chronograf.Organization{ + chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, nil + }, }, UsersStore: &mocks.UsersStore{ NumF: func(ctx context.Context) (int, error) { @@ -476,6 +494,24 @@ func TestService_Me(t *testing.T) { Mappings: []chronograf.Mapping{}, }, nil }, + AllF: func(ctx context.Context) ([]chronograf.Organization, error) { + return []chronograf.Organization{ + chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, nil + }, }, UsersStore: &mocks.UsersStore{ NumF: func(ctx context.Context) (int, error) { @@ -539,6 +575,24 @@ func TestService_Me(t *testing.T) { Mappings: []chronograf.Mapping{}, }, nil }, + AllF: func(ctx context.Context) ([]chronograf.Organization, error) { + return []chronograf.Organization{ + chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, nil + }, }, UsersStore: &mocks.UsersStore{ NumF: func(ctx context.Context) (int, error) { @@ -586,6 +640,7 @@ func TestService_Me(t *testing.T) { DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ ID: "0", + Name: "The Bad Place", Public: true, Mappings: []chronograf.Mapping{}, }, nil @@ -598,6 +653,24 @@ func TestService_Me(t *testing.T) { Mappings: []chronograf.Mapping{}, }, nil }, + AllF: func(ctx context.Context) ([]chronograf.Organization, error) { + return []chronograf.Organization{ + chronograf.Organization{ + ID: "0", + Name: "The Bad Place", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, nil + }, }, UsersStore: &mocks.UsersStore{ NumF: func(ctx context.Context) (int, error) { @@ -716,6 +789,101 @@ func TestService_Me(t *testing.T) { wantContentType: "application/json", wantBody: `{"code":403,"message":"This organization is private. To gain access, you must be explicitly added by an administrator."}`, }, + { + name: "New user multiple mappings", + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest("GET", "http://example.com/foo", nil), + }, + fields: fields{ + UseAuth: true, + Logger: log.New(log.DebugLevel), + ConfigStore: &mocks.ConfigStore{ + Config: &chronograf.Config{ + Auth: chronograf.AuthConfig{ + SuperAdminNewUsers: false, + }, + }, + }, + OrganizationsStore: &mocks.OrganizationsStore{ + DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{}, + }, nil + }, + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{}, + }, nil + }, + AllF: func(ctx context.Context) ([]chronograf.Organization, error) { + return []chronograf.Organization{ + chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + chronograf.Organization{ + ID: "1", + Name: "another org", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.EditorRoleName, + }, + }, + }, + }, nil + }, + }, + UsersStore: &mocks.UsersStore{ + NumF: func(ctx context.Context) (int, error) { + // This function gets to verify that there is at least one first user + return 1, nil + }, + GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { + if q.Name == nil || q.Provider == nil || q.Scheme == nil { + return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme") + } + return nil, chronograf.ErrUserNotFound + }, + AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { + return u, nil + }, + UpdateF: func(ctx context.Context, u *chronograf.User) error { + return nil + }, + }, + }, + principal: oauth2.Principal{ + Subject: "secret", + Issuer: "auth0", + }, + wantStatus: http.StatusOK, + wantContentType: "application/json", + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"},{"name":"editor","organization":"1"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]},{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + }, } for _, tt := range tests { tt.args.r = tt.args.r.WithContext(context.WithValue(context.Background(), oauth2.PrincipalKey, tt.principal))