2016-11-17 23:57:46 +00:00
package server
import (
2017-11-06 15:48:31 +00:00
"context"
2017-03-10 19:24:48 +00:00
"encoding/json"
2016-11-17 23:57:46 +00:00
"fmt"
"net/http"
2017-10-09 22:19:46 +00:00
"sort"
2017-10-18 18:17:42 +00:00
"strconv"
2016-11-17 23:57:46 +00:00
2017-03-10 19:24:48 +00:00
"github.com/bouk/httprouter"
2020-04-03 17:39:20 +00:00
"github.com/influxdata/influxdb/v2/chronograf"
"github.com/influxdata/influxdb/v2/chronograf/roles"
2016-11-17 23:57:46 +00:00
)
2017-10-09 21:16:24 +00:00
type userRequest struct {
2017-11-01 00:58:40 +00:00
ID uint64 ` json:"id,string" `
Name string ` json:"name" `
Provider string ` json:"provider" `
Scheme string ` json:"scheme" `
SuperAdmin bool ` json:"superAdmin" `
Roles [ ] chronograf . Role ` json:"roles" `
2017-02-15 22:28:17 +00:00
}
2017-10-09 21:16:24 +00:00
func ( r * userRequest ) ValidCreate ( ) error {
if r . Name == "" {
2018-11-21 14:22:35 +00:00
return fmt . Errorf ( "name required on Chronograf User request body" )
2017-03-10 19:24:48 +00:00
}
2017-10-09 21:16:24 +00:00
if r . Provider == "" {
2018-11-21 14:22:35 +00:00
return fmt . Errorf ( "provider required on Chronograf User request body" )
2017-03-10 19:24:48 +00:00
}
2017-10-09 21:16:24 +00:00
if r . Scheme == "" {
2018-11-21 14:22:35 +00:00
return fmt . Errorf ( "scheme required on Chronograf User request body" )
2017-03-10 19:24:48 +00:00
}
2017-10-19 18:17:40 +00:00
// TODO: This Scheme value is hard-coded temporarily since we only currently
// support OAuth2. This hard-coding should be removed whenever we add
// support for other authentication schemes.
2017-10-24 23:17:59 +00:00
r . Scheme = "oauth2"
2017-10-13 19:34:30 +00:00
return r . ValidRoles ( )
2017-10-09 21:16:24 +00:00
}
2017-03-10 19:24:48 +00:00
2017-10-09 21:16:24 +00:00
func ( r * userRequest ) ValidUpdate ( ) error {
2018-01-12 17:37:30 +00:00
if r . Roles == nil {
2018-11-21 14:22:35 +00:00
return fmt . Errorf ( "no Roles to update" )
2017-02-15 22:28:17 +00:00
}
2017-10-13 19:34:30 +00:00
return r . ValidRoles ( )
}
func ( r * userRequest ) ValidRoles ( ) error {
2017-10-16 18:58:16 +00:00
if len ( r . Roles ) > 0 {
2018-01-12 17:37:30 +00:00
orgs := map [ string ] bool { }
2017-10-13 19:34:30 +00:00
for _ , r := range r . Roles {
2017-11-22 19:49:44 +00:00
if r . Organization == "" {
return fmt . Errorf ( "no organization was provided" )
}
2017-11-03 16:06:18 +00:00
if _ , ok := orgs [ r . Organization ] ; ok {
return fmt . Errorf ( "duplicate organization %q in roles" , r . Organization )
}
orgs [ r . Organization ] = true
2017-10-16 23:26:08 +00:00
switch r . Name {
2018-01-12 17:27:55 +00:00
case roles . MemberRoleName , roles . ViewerRoleName , roles . EditorRoleName , roles . AdminRoleName , roles . WildcardRoleName :
2017-10-16 23:26:08 +00:00
continue
default :
2018-11-21 14:22:35 +00:00
return fmt . Errorf ( "unknown role %s. Valid roles are 'member', 'viewer', 'editor', 'admin', and '*'" , r . Name )
2017-10-13 19:34:30 +00:00
}
}
}
2017-10-09 21:16:24 +00:00
return nil
2016-11-17 23:57:46 +00:00
}
2017-10-09 21:16:24 +00:00
type userResponse struct {
2017-11-01 00:58:40 +00:00
Links selfLinks ` json:"links" `
ID uint64 ` json:"id,string" `
Name string ` json:"name" `
Provider string ` json:"provider" `
Scheme string ` json:"scheme" `
SuperAdmin bool ` json:"superAdmin" `
Roles [ ] chronograf . Role ` json:"roles" `
2017-10-09 21:16:24 +00:00
}
2017-03-10 19:24:48 +00:00
2018-01-16 21:45:58 +00:00
func newUserResponse ( u * chronograf . User , org string ) * userResponse {
2017-10-16 23:26:08 +00:00
// This ensures that any user response with no roles returns an empty array instead of
// null when marshaled into JSON. That way, JavaScript doesn't need any guard on the
// key existing and it can simply be iterated over.
if u . Roles == nil {
u . Roles = [ ] chronograf . Role { }
2017-10-13 19:34:30 +00:00
}
2018-01-16 21:45:58 +00:00
var selfLink string
if org != "" {
selfLink = fmt . Sprintf ( "/chronograf/v1/organizations/%s/users/%d" , org , u . ID )
} else {
selfLink = fmt . Sprintf ( "/chronograf/v1/users/%d" , u . ID )
2018-01-09 18:43:33 +00:00
}
2017-10-09 21:16:24 +00:00
return & userResponse {
2017-11-02 23:24:16 +00:00
ID : u . ID ,
Name : u . Name ,
Provider : u . Provider ,
Scheme : u . Scheme ,
Roles : u . Roles ,
SuperAdmin : u . SuperAdmin ,
2017-10-09 21:16:24 +00:00
Links : selfLinks {
2018-01-09 18:43:33 +00:00
Self : selfLink ,
2017-10-09 21:16:24 +00:00
} ,
2017-03-10 19:24:48 +00:00
}
}
2017-10-09 22:03:50 +00:00
type usersResponse struct {
Links selfLinks ` json:"links" `
Users [ ] * userResponse ` json:"users" `
}
2018-01-16 21:45:58 +00:00
func newUsersResponse ( users [ ] chronograf . User , org string ) * usersResponse {
2017-10-10 18:30:57 +00:00
usersResp := make ( [ ] * userResponse , len ( users ) )
2017-10-09 22:03:50 +00:00
for i , user := range users {
2018-01-16 21:45:58 +00:00
usersResp [ i ] = newUserResponse ( & user , org )
2017-10-09 22:03:50 +00:00
}
2017-10-10 18:30:57 +00:00
sort . Slice ( usersResp , func ( i , j int ) bool {
return usersResp [ i ] . ID < usersResp [ j ] . ID
} )
2018-01-09 18:43:33 +00:00
2018-01-16 21:45:58 +00:00
var selfLink string
if org != "" {
selfLink = fmt . Sprintf ( "/chronograf/v1/organizations/%s/users" , org )
} else {
selfLink = "/chronograf/v1/users"
2018-01-09 18:43:33 +00:00
}
2017-10-09 22:03:50 +00:00
return & usersResponse {
Users : usersResp ,
Links : selfLinks {
2018-01-09 18:43:33 +00:00
Self : selfLink ,
2017-10-09 22:03:50 +00:00
} ,
}
}
2017-10-09 22:28:39 +00:00
// UserID retrieves a Chronograf user with ID from store
2017-10-09 21:16:24 +00:00
func ( s * Service ) UserID ( w http . ResponseWriter , r * http . Request ) {
2017-03-10 19:24:48 +00:00
ctx := r . Context ( )
2017-02-24 05:26:09 +00:00
2017-10-18 18:17:42 +00:00
idStr := httprouter . GetParamFromContext ( ctx , "id" )
id , err := strconv . ParseUint ( idStr , 10 , 64 )
if err != nil {
Error ( w , http . StatusBadRequest , fmt . Sprintf ( "invalid user id: %s" , err . Error ( ) ) , s . Logger )
return
}
2017-10-31 20:41:17 +00:00
user , err := s . Store . Users ( ctx ) . Get ( ctx , chronograf . UserQuery { ID : & id } )
2016-11-17 23:57:46 +00:00
if err != nil {
2017-10-09 21:16:24 +00:00
Error ( w , http . StatusBadRequest , err . Error ( ) , s . Logger )
2017-03-10 19:24:48 +00:00
return
}
2018-01-16 21:45:58 +00:00
orgID := httprouter . GetParamFromContext ( ctx , "oid" )
res := newUserResponse ( user , orgID )
location ( w , res . Links . Self )
2017-10-09 21:16:24 +00:00
encodeJSON ( w , http . StatusOK , res , s . Logger )
2017-03-10 19:24:48 +00:00
}
2017-10-09 22:28:39 +00:00
// NewUser adds a new Chronograf user to store
2017-10-09 21:16:24 +00:00
func ( s * Service ) NewUser ( w http . ResponseWriter , r * http . Request ) {
2017-03-10 19:24:48 +00:00
var req userRequest
if err := json . NewDecoder ( r . Body ) . Decode ( & req ) ; err != nil {
2017-10-09 21:16:24 +00:00
invalidJSON ( w , s . Logger )
2016-11-17 23:57:46 +00:00
return
}
2017-02-17 19:37:00 +00:00
2017-10-09 21:16:24 +00:00
if err := req . ValidCreate ( ) ; err != nil {
invalidData ( w , err , s . Logger )
2016-11-17 23:57:46 +00:00
return
}
2017-10-09 21:16:24 +00:00
ctx := r . Context ( )
2017-12-14 19:11:51 +00:00
2017-12-14 21:34:19 +00:00
serverCtx := serverContext ( ctx )
cfg , err := s . Store . Config ( serverCtx ) . Get ( serverCtx )
2017-12-14 19:11:51 +00:00
if err != nil {
Error ( w , http . StatusInternalServerError , err . Error ( ) , s . Logger )
return
}
2018-01-12 17:27:55 +00:00
if err := s . validRoles ( serverCtx , req . Roles ) ; err != nil {
invalidData ( w , err , s . Logger )
return
}
2016-11-17 23:57:46 +00:00
user := & chronograf . User {
2017-10-09 21:16:24 +00:00
Name : req . Name ,
Provider : req . Provider ,
Scheme : req . Scheme ,
2017-10-16 23:26:08 +00:00
Roles : req . Roles ,
2017-03-10 19:24:48 +00:00
}
2017-12-14 19:36:26 +00:00
if cfg . Auth . SuperAdminNewUsers {
req . SuperAdmin = true
}
2017-11-06 15:48:31 +00:00
if err := setSuperAdmin ( ctx , req , user ) ; err != nil {
Error ( w , http . StatusUnauthorized , err . Error ( ) , s . Logger )
2017-11-03 16:51:33 +00:00
return
2017-11-01 13:12:19 +00:00
}
2017-10-31 20:41:17 +00:00
res , err := s . Store . Users ( ctx ) . Add ( ctx , user )
2016-11-17 23:57:46 +00:00
if err != nil {
2017-10-09 21:16:24 +00:00
Error ( w , http . StatusBadRequest , err . Error ( ) , s . Logger )
2016-11-17 23:57:46 +00:00
return
}
2018-01-16 21:45:58 +00:00
orgID := httprouter . GetParamFromContext ( ctx , "oid" )
cu := newUserResponse ( res , orgID )
2017-10-10 21:40:45 +00:00
location ( w , cu . Links . Self )
2017-10-09 21:16:24 +00:00
encodeJSON ( w , http . StatusCreated , cu , s . Logger )
2016-11-17 23:57:46 +00:00
}
2017-03-10 19:24:48 +00:00
2017-10-09 22:28:39 +00:00
// RemoveUser deletes a Chronograf user from store
2017-10-09 21:16:24 +00:00
func ( s * Service ) RemoveUser ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
2017-10-18 18:17:42 +00:00
idStr := httprouter . GetParamFromContext ( ctx , "id" )
id , err := strconv . ParseUint ( idStr , 10 , 64 )
if err != nil {
Error ( w , http . StatusBadRequest , fmt . Sprintf ( "invalid user id: %s" , err . Error ( ) ) , s . Logger )
return
}
2017-03-10 19:24:48 +00:00
2017-10-31 20:41:17 +00:00
u , err := s . Store . Users ( ctx ) . Get ( ctx , chronograf . UserQuery { ID : & id } )
2017-03-10 19:24:48 +00:00
if err != nil {
2017-10-09 21:16:24 +00:00
Error ( w , http . StatusNotFound , err . Error ( ) , s . Logger )
2017-10-20 19:42:51 +00:00
return
2017-03-10 19:24:48 +00:00
}
2017-10-31 20:41:17 +00:00
if err := s . Store . Users ( ctx ) . Delete ( ctx , u ) ; err != nil {
2017-10-09 21:16:24 +00:00
Error ( w , http . StatusBadRequest , err . Error ( ) , s . Logger )
2017-10-20 19:42:51 +00:00
return
2017-03-10 19:24:48 +00:00
}
2017-10-09 21:16:24 +00:00
w . WriteHeader ( http . StatusNoContent )
2017-03-10 19:24:48 +00:00
}
2017-10-09 22:28:39 +00:00
// UpdateUser updates a Chronograf user in store
2017-10-09 21:16:24 +00:00
func ( s * Service ) UpdateUser ( w http . ResponseWriter , r * http . Request ) {
var req userRequest
if err := json . NewDecoder ( r . Body ) . Decode ( & req ) ; err != nil {
invalidJSON ( w , s . Logger )
return
2017-03-10 19:24:48 +00:00
}
2017-10-09 21:16:24 +00:00
ctx := r . Context ( )
2017-10-18 18:17:42 +00:00
idStr := httprouter . GetParamFromContext ( ctx , "id" )
id , err := strconv . ParseUint ( idStr , 10 , 64 )
if err != nil {
Error ( w , http . StatusBadRequest , fmt . Sprintf ( "invalid user id: %s" , err . Error ( ) ) , s . Logger )
return
}
2017-03-10 19:24:48 +00:00
2017-11-22 20:20:48 +00:00
if err := req . ValidUpdate ( ) ; err != nil {
invalidData ( w , err , s . Logger )
return
}
2017-10-31 20:41:17 +00:00
u , err := s . Store . Users ( ctx ) . Get ( ctx , chronograf . UserQuery { ID : & id } )
2017-10-09 21:16:24 +00:00
if err != nil {
Error ( w , http . StatusNotFound , err . Error ( ) , s . Logger )
2017-10-20 19:42:51 +00:00
return
2017-03-10 19:24:48 +00:00
}
2018-01-12 17:27:55 +00:00
serverCtx := serverContext ( ctx )
if err := s . validRoles ( serverCtx , req . Roles ) ; err != nil {
invalidData ( w , err , s . Logger )
return
}
2017-10-24 21:20:24 +00:00
// ValidUpdate should ensure that req.Roles is not nil
u . Roles = req . Roles
2017-03-10 19:24:48 +00:00
2017-11-22 20:20:48 +00:00
// If the request contains a name, it must be the same as the
// one on the user. This is particularly useful to the front-end
// because they would like to provide the whole user object,
// including the name, provider, and scheme in update requests.
// But currently, it is not possible to change name, provider, or
// scheme via the API.
if req . Name != "" && req . Name != u . Name {
2018-11-21 14:22:35 +00:00
err := fmt . Errorf ( "cannot update Name" )
2017-11-22 20:20:48 +00:00
invalidData ( w , err , s . Logger )
return
}
if req . Provider != "" && req . Provider != u . Provider {
2018-11-21 14:22:35 +00:00
err := fmt . Errorf ( "cannot update Provider" )
2017-11-22 20:20:48 +00:00
invalidData ( w , err , s . Logger )
return
}
if req . Scheme != "" && req . Scheme != u . Scheme {
2018-11-21 14:22:35 +00:00
err := fmt . Errorf ( "cannot update Scheme" )
2017-11-22 20:20:48 +00:00
invalidData ( w , err , s . Logger )
return
}
2017-12-20 23:17:08 +00:00
// Don't allow SuperAdmins to modify their own SuperAdmin status.
// Allowing them to do so could result in an application where there
// are no super admins.
ctxUser , ok := hasUserContext ( ctx )
if ! ok {
Error ( w , http . StatusInternalServerError , "failed to retrieve user from context" , s . Logger )
return
}
// If the user being updated is the user making the request and they are
// changing their SuperAdmin status, return an unauthorized error
2018-11-01 19:03:51 +00:00
if ctxUser . ID == u . ID && u . SuperAdmin && ! req . SuperAdmin {
2017-12-20 23:17:08 +00:00
Error ( w , http . StatusUnauthorized , "user cannot modify their own SuperAdmin status" , s . Logger )
return
}
2017-11-06 15:48:31 +00:00
if err := setSuperAdmin ( ctx , req , u ) ; err != nil {
Error ( w , http . StatusUnauthorized , err . Error ( ) , s . Logger )
2017-11-03 16:51:33 +00:00
return
2017-11-01 14:39:09 +00:00
}
2017-10-31 20:41:17 +00:00
err = s . Store . Users ( ctx ) . Update ( ctx , u )
2017-10-09 21:16:24 +00:00
if err != nil {
Error ( w , http . StatusBadRequest , err . Error ( ) , s . Logger )
return
2017-03-10 19:24:48 +00:00
}
2018-01-16 21:45:58 +00:00
orgID := httprouter . GetParamFromContext ( ctx , "oid" )
cu := newUserResponse ( u , orgID )
2017-10-10 21:40:45 +00:00
location ( w , cu . Links . Self )
2017-10-09 21:16:24 +00:00
encodeJSON ( w , http . StatusOK , cu , s . Logger )
2017-03-10 19:24:48 +00:00
}
2017-10-09 22:03:50 +00:00
2017-10-09 22:28:39 +00:00
// Users retrieves all Chronograf users from store
2017-10-09 22:03:50 +00:00
func ( s * Service ) Users ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
2017-10-31 20:41:17 +00:00
users , err := s . Store . Users ( ctx ) . All ( ctx )
2017-10-09 22:03:50 +00:00
if err != nil {
Error ( w , http . StatusBadRequest , err . Error ( ) , s . Logger )
return
}
2018-01-16 21:45:58 +00:00
orgID := httprouter . GetParamFromContext ( ctx , "oid" )
res := newUsersResponse ( users , orgID )
2017-10-09 22:03:50 +00:00
encodeJSON ( w , http . StatusOK , res , s . Logger )
}
2017-11-06 15:48:31 +00:00
func setSuperAdmin ( ctx context . Context , req userRequest , user * chronograf . User ) error {
2017-12-04 21:53:30 +00:00
// At a high level, this function checks the following
// 1. Is the user making the request a SuperAdmin.
// If they are, allow them to make whatever changes they please.
//
// 2. Is the user making the request trying to change the SuperAdmin
// status. If so, return an error.
//
// 3. If none of the above are the case, let the user make whichever
// changes were requested.
2017-11-06 15:48:31 +00:00
// Only allow users to set SuperAdmin if they have the superadmin context
2019-01-08 00:37:16 +00:00
// TODO(desa): Refactor this https://github.com/influxdata/influxdb/chronograf/issues/2207
2017-11-06 15:48:31 +00:00
if isSuperAdmin := hasSuperAdminContext ( ctx ) ; isSuperAdmin {
user . SuperAdmin = req . SuperAdmin
2017-12-04 21:53:30 +00:00
} else if ! isSuperAdmin && ( user . SuperAdmin != req . SuperAdmin ) {
2017-11-06 15:48:31 +00:00
// If req.SuperAdmin has been set, and the request was not made with the SuperAdmin
// context, return error
2019-01-08 00:37:16 +00:00
return fmt . Errorf ( "user does not have authorization required to set SuperAdmin status. See https://github.com/influxdata/influxdb/chronograf/issues/2601 for more information" )
2017-11-06 15:48:31 +00:00
}
return nil
}
2018-01-12 17:27:55 +00:00
func ( s * Service ) validRoles ( ctx context . Context , rs [ ] chronograf . Role ) error {
for i , role := range rs {
// verify that the organization exists
org , err := s . Store . Organizations ( ctx ) . Get ( ctx , chronograf . OrganizationQuery { ID : & role . Organization } )
if err != nil {
return err
}
if role . Name == roles . WildcardRoleName {
role . Name = org . DefaultRole
rs [ i ] = role
}
}
return nil
}