From 311c68f4578e82b970c879e20409a7bcd0ebc780 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Thu, 2 Nov 2017 11:59:53 -0400 Subject: [PATCH] Add CurrentOrganization & Organizations to me resp Remove CurrentOrganization from chronograf.User --- chronograf.go | 19 +++++++------ server/me.go | 68 ++++++++++++++++++++++++++++++++++++++++++++--- server/me_test.go | 43 ++++++++++++++++++++++++------ 3 files changed, 109 insertions(+), 21 deletions(-) diff --git a/chronograf.go b/chronograf.go index 0491f1472f..5a83978682 100644 --- a/chronograf.go +++ b/chronograf.go @@ -618,15 +618,14 @@ type Scope string // User represents an authenticated user. type User struct { - ID uint64 `json:"id,string,omitempty"` - Name string `json:"name"` - Passwd string `json:"password,omitempty"` - Permissions Permissions `json:"permissions,omitempty"` - Roles []Role `json:"roles,omitempty"` - Provider string `json:"provider,omitempty"` - Scheme string `json:"scheme,omitempty"` - CurrentOrganization string `json:"currentOrganization,omitempty"` - SuperAdmin bool `json:"superAdmin,omitempty"` + ID uint64 `json:"id,string,omitempty"` + Name string `json:"name"` + Passwd string `json:"password,omitempty"` + Permissions Permissions `json:"permissions,omitempty"` + Roles []Role `json:"roles,omitempty"` + Provider string `json:"provider,omitempty"` + Scheme string `json:"scheme,omitempty"` + SuperAdmin bool `json:"superAdmin,omitempty"` } // UserQuery represents the attributes that a user may be retrieved by. @@ -771,7 +770,7 @@ type LayoutsStore interface { // Organization is a group of resources under a common name type Organization struct { - ID uint64 `json:"id"` + ID uint64 `json:"id,string"` Name string `json:"name"` } diff --git a/server/me.go b/server/me.go index 0888cce292..2cdcba1c16 100644 --- a/server/me.go +++ b/server/me.go @@ -18,7 +18,9 @@ type meLinks struct { type meResponse struct { *chronograf.User - Links meLinks `json:"links"` + Links meLinks `json:"links"` + Organizations []chronograf.Organization `json:"organizations,omitempty"` + CurrentOrganization *chronograf.Organization `json:"currentOrganization,omitempty"` } // If new user response is nil, return an empty meResponse because it @@ -65,6 +67,10 @@ func getValidPrincipal(ctx context.Context) (oauth2.Principal, error) { if p.Issuer == "" { return oauth2.Principal{}, fmt.Errorf("Token not found") } + // TODO(desa): make this default org + if p.Organization == "" { + p.Organization = "0" + } return p, nil } @@ -176,8 +182,24 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { } if usr != nil { - usr.CurrentOrganization = p.Organization + orgs, err := s.usersOrganizations(ctx, usr) + if err != nil { + unknownErrorWithMessage(w, err, s.Logger) + return + } + orgID, err := parseOrganizationID(p.Organization) + if err != nil { + unknownErrorWithMessage(w, err, s.Logger) + return + } + currentOrg, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &orgID}) + if err != nil { + unknownErrorWithMessage(w, err, s.Logger) + return + } res := newMeResponse(usr) + res.Organizations = orgs + res.CurrentOrganization = currentOrg encodeJSON(w, http.StatusOK, res, s.Logger) return } @@ -207,8 +229,24 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { return } - newUser.CurrentOrganization = p.Organization + orgs, err := s.usersOrganizations(ctx, newUser) + if err != nil { + unknownErrorWithMessage(w, err, s.Logger) + return + } + orgID, err := parseOrganizationID(p.Organization) + if err != nil { + unknownErrorWithMessage(w, err, s.Logger) + return + } + currentOrg, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &orgID}) + if err != nil { + unknownErrorWithMessage(w, err, s.Logger) + return + } res := newMeResponse(newUser) + res.Organizations = orgs + res.CurrentOrganization = currentOrg encodeJSON(w, http.StatusOK, res, s.Logger) } @@ -222,3 +260,27 @@ func (s *Service) firstUser() bool { return len(users) == 0 } + +func (s *Service) usersOrganizations(ctx context.Context, u *chronograf.User) ([]chronograf.Organization, error) { + if u == nil { + // TODO(desa): better error + return nil, fmt.Errorf("user was nil") + } + + orgIDs := map[string]bool{} + for _, role := range u.Roles { + orgIDs[role.Organization] = true + } + + orgs := []chronograf.Organization{} + for orgID, _ := range orgIDs { + id, err := parseOrganizationID(orgID) + org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &id}) + if err != nil { + return nil, err + } + orgs = append(orgs, *org) + } + + return orgs, nil +} diff --git a/server/me_test.go b/server/me_test.go index 22277cd8fb..7f45412991 100644 --- a/server/me_test.go +++ b/server/me_test.go @@ -20,9 +20,10 @@ type MockUsers struct{} func TestService_Me(t *testing.T) { type fields struct { - UsersStore chronograf.UsersStore - Logger chronograf.Logger - UseAuth bool + UsersStore chronograf.UsersStore + OrganizationsStore chronograf.OrganizationsStore + Logger chronograf.Logger + UseAuth bool } type args struct { w *httptest.ResponseRecorder @@ -46,6 +47,14 @@ func TestService_Me(t *testing.T) { fields: fields{ UseAuth: true, Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: 0, + Name: "The Bad Place", + }, nil + }, + }, UsersStore: &mocks.UsersStore{ AllF: func(ctx context.Context) ([]chronograf.User, error) { // This function gets to verify that there is at least one first user @@ -69,7 +78,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/me"}} + wantBody: `{"name":"me","provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/me"},"currentOrganization":{"id":"0","name":"The Bad Place"}} `, }, { @@ -81,6 +90,14 @@ func TestService_Me(t *testing.T) { fields: fields{ UseAuth: true, Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: 0, + Name: "The Bad Place", + }, nil + }, + }, UsersStore: &mocks.UsersStore{ AllF: func(ctx context.Context) ([]chronograf.User, error) { // This function gets to verify that there is at least one first user @@ -103,7 +120,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"member","organization":"\"0\""}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/secret"}} + wantBody: `{"name":"secret","roles":[{"name":"member","organization":"\"0\""}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/secret"},"organizations":[{"id":"0","name":"The Bad Place"}],"currentOrganization":{"id":"0","name":"The Bad Place"}} `, }, { @@ -114,6 +131,14 @@ func TestService_Me(t *testing.T) { }, fields: fields{ UseAuth: true, + OrganizationsStore: &mocks.OrganizationsStore{ + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: 0, + Name: "The Bad Place", + }, nil + }, + }, UsersStore: &mocks.UsersStore{ AllF: func(ctx context.Context) ([]chronograf.User, error) { // This function gets to verify that there is at least one first user @@ -172,7 +197,8 @@ func TestService_Me(t *testing.T) { tt.args.r = tt.args.r.WithContext(context.WithValue(context.Background(), oauth2.PrincipalKey, tt.principal)) s := &Service{ Store: &mocks.Store{ - UsersStore: tt.fields.UsersStore, + UsersStore: tt.fields.UsersStore, + OrganizationsStore: tt.fields.OrganizationsStore, }, Logger: tt.fields.Logger, UseAuth: tt.fields.UseAuth, @@ -267,7 +293,7 @@ func TestService_MeOrganizations(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"admin","organization":"\"1337\""}],"provider":"github","scheme":"oauth2","currentOrganization":"1337","links":{"self":"/chronograf/v1/users/me"}}`, + wantBody: `{"name":"me","roles":[{"name":"admin","organization":"\"1337\""}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/me"},"organizations":[{"id":"1337","name":"The ShillBillThrilliettas"}],"currentOrganization":{"id":"1337","name":"The ShillBillThrilliettas"}}`, }, { name: "Change the current User's organization", @@ -319,7 +345,8 @@ func TestService_MeOrganizations(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"admin","organization":"\"1337\""}],"provider":"github","scheme":"oauth2","currentOrganization":"1337","links":{"self":"/chronograf/v1/users/me"}}`, + wantBody: `{"name":"me","roles":[{"name":"admin","organization":"\"1337\""}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/me"},"organizations":[{"id":"1337","name":"The ThrillShilliettos"}],"currentOrganization":{"id":"1337","name":"The ThrillShilliettos"}} +`, }, { name: "Unable to find requested user in valid organization",