Merge pull request #2215 from influxdata/multitenancy_add_orgs_to_me
Add CurrentOrganization & Organizations to me respmultitenancy_temp_server1
commit
14beabb6fd
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
68
server/me.go
68
server/me.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue