Merge pull request #2215 from influxdata/multitenancy_add_orgs_to_me

Add CurrentOrganization & Organizations to me resp
multitenancy_temp_server1
Michael Desa 2017-11-02 15:38:49 -04:00 committed by GitHub
commit 14beabb6fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 109 additions and 21 deletions

View File

@ -619,15 +619,14 @@ type Scope string
// User represents an authenticated user. // User represents an authenticated user.
type User struct { type User struct {
ID uint64 `json:"id,string,omitempty"` ID uint64 `json:"id,string,omitempty"`
Name string `json:"name"` Name string `json:"name"`
Passwd string `json:"password,omitempty"` Passwd string `json:"password,omitempty"`
Permissions Permissions `json:"permissions,omitempty"` Permissions Permissions `json:"permissions,omitempty"`
Roles []Role `json:"roles,omitempty"` Roles []Role `json:"roles,omitempty"`
Provider string `json:"provider,omitempty"` Provider string `json:"provider,omitempty"`
Scheme string `json:"scheme,omitempty"` Scheme string `json:"scheme,omitempty"`
CurrentOrganization string `json:"currentOrganization,omitempty"` SuperAdmin bool `json:"superAdmin,omitempty"`
SuperAdmin bool `json:"superAdmin,omitempty"`
} }
// UserQuery represents the attributes that a user may be retrieved by. // UserQuery represents the attributes that a user may be retrieved by.
@ -772,7 +771,7 @@ type LayoutsStore interface {
// Organization is a group of resources under a common name // Organization is a group of resources under a common name
type Organization struct { type Organization struct {
ID uint64 `json:"id"` ID uint64 `json:"id,string"`
Name string `json:"name"` Name string `json:"name"`
} }

View File

@ -18,7 +18,9 @@ type meLinks struct {
type meResponse struct { type meResponse struct {
*chronograf.User *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 // 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 == "" { if p.Issuer == "" {
return oauth2.Principal{}, fmt.Errorf("Token not found") return oauth2.Principal{}, fmt.Errorf("Token not found")
} }
// TODO(desa): make this default org
if p.Organization == "" {
p.Organization = "0"
}
return p, nil return p, nil
} }
@ -176,8 +182,24 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
} }
if usr != nil { 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 := newMeResponse(usr)
res.Organizations = orgs
res.CurrentOrganization = currentOrg
encodeJSON(w, http.StatusOK, res, s.Logger) encodeJSON(w, http.StatusOK, res, s.Logger)
return return
} }
@ -207,8 +229,24 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
return 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 := newMeResponse(newUser)
res.Organizations = orgs
res.CurrentOrganization = currentOrg
encodeJSON(w, http.StatusOK, res, s.Logger) encodeJSON(w, http.StatusOK, res, s.Logger)
} }
@ -222,3 +260,27 @@ func (s *Service) firstUser() bool {
return len(users) == 0 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
}

View File

@ -20,9 +20,10 @@ type MockUsers struct{}
func TestService_Me(t *testing.T) { func TestService_Me(t *testing.T) {
type fields struct { type fields struct {
UsersStore chronograf.UsersStore UsersStore chronograf.UsersStore
Logger chronograf.Logger OrganizationsStore chronograf.OrganizationsStore
UseAuth bool Logger chronograf.Logger
UseAuth bool
} }
type args struct { type args struct {
w *httptest.ResponseRecorder w *httptest.ResponseRecorder
@ -46,6 +47,14 @@ func TestService_Me(t *testing.T) {
fields: fields{ fields: fields{
UseAuth: true, UseAuth: true,
Logger: log.New(log.DebugLevel), 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{ UsersStore: &mocks.UsersStore{
AllF: func(ctx context.Context) ([]chronograf.User, error) { AllF: func(ctx context.Context) ([]chronograf.User, error) {
// This function gets to verify that there is at least one first user // 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, wantStatus: http.StatusOK,
wantContentType: "application/json", 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{ fields: fields{
UseAuth: true, UseAuth: true,
Logger: log.New(log.DebugLevel), 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{ UsersStore: &mocks.UsersStore{
AllF: func(ctx context.Context) ([]chronograf.User, error) { AllF: func(ctx context.Context) ([]chronograf.User, error) {
// This function gets to verify that there is at least one first user // 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, wantStatus: http.StatusOK,
wantContentType: "application/json", 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{ fields: fields{
UseAuth: true, 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{ UsersStore: &mocks.UsersStore{
AllF: func(ctx context.Context) ([]chronograf.User, error) { AllF: func(ctx context.Context) ([]chronograf.User, error) {
// This function gets to verify that there is at least one first user // 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)) tt.args.r = tt.args.r.WithContext(context.WithValue(context.Background(), oauth2.PrincipalKey, tt.principal))
s := &Service{ s := &Service{
Store: &mocks.Store{ Store: &mocks.Store{
UsersStore: tt.fields.UsersStore, UsersStore: tt.fields.UsersStore,
OrganizationsStore: tt.fields.OrganizationsStore,
}, },
Logger: tt.fields.Logger, Logger: tt.fields.Logger,
UseAuth: tt.fields.UseAuth, UseAuth: tt.fields.UseAuth,
@ -267,7 +293,7 @@ func TestService_MeOrganizations(t *testing.T) {
}, },
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantContentType: "application/json", 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", name: "Change the current User's organization",
@ -319,7 +345,8 @@ func TestService_MeOrganizations(t *testing.T) {
}, },
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantContentType: "application/json", 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", name: "Unable to find requested user in valid organization",