Parcourir la source

untested PUT /api/users/{id}

Alexander Belanger il y a 5 ans
Parent
commit
0502594c89

+ 58 - 0
internal/forms/user.go

@@ -0,0 +1,58 @@
+package forms
+
+import (
+	"github.com/porter-dev/porter/internal/kubernetes"
+	"github.com/porter-dev/porter/internal/models"
+	"gopkg.in/yaml.v2"
+)
+
+// WriteUserForm is a generic form for write operations to the User model
+type WriteUserForm interface {
+	ToUser() (*models.User, error)
+}
+
+// CreateUserForm represents the accepted values for creating a user
+type CreateUserForm struct {
+	WriteUserForm
+	Email    string `json:"email" form:"required,max=255,email"`
+	Password string `json:"password" form:"required,max=255"`
+}
+
+// UpdateUserForm represents the accepted values for updating a user
+//
+// ID is a query parameter, the other two are sent in JSON body
+type UpdateUserForm struct {
+	WriteUserForm
+	ID              uint64   `form:"required"`
+	RawKubeConfig   string   `json:"rawKubeConfig" form:"required"`
+	AllowedClusters []string `json:"allowedClusters" form:"required"`
+}
+
+// ToUser converts a CreateUserForm to models.User
+//
+// TODO -- PASSWORD HASHING HERE
+func (cuf *CreateUserForm) ToUser() (*models.User, error) {
+	return &models.User{
+		Email:    cuf.Email,
+		Password: cuf.Password,
+	}, nil
+}
+
+// ToUser converts an UpdateUserForm to models.User by parsing the kubeconfig
+// and the allowed clusters to generate a list of ClusterConfigs.
+func (uuf *UpdateUserForm) ToUser() (*models.User, error) {
+	conf := kubernetes.KubeConfig{}
+	rawBytes := []byte(uuf.RawKubeConfig)
+	err := yaml.Unmarshal(rawBytes, &conf)
+
+	if err != nil {
+		return nil, err
+	}
+
+	clusters := conf.ToClusterConfigs(uuf.AllowedClusters)
+
+	return &models.User{
+		Clusters:      clusters,
+		RawKubeConfig: rawBytes,
+	}, nil
+}

+ 23 - 4
internal/kubernetes/kubeconfig.go

@@ -34,14 +34,19 @@ type KubeConfig struct {
 
 // ToClusterConfigs converts a KubeConfig to a set of ClusterConfigExternals by
 // joining users and clusters on the context.
-func (k *KubeConfig) ToClusterConfigs() []*models.ClusterConfigExternal {
-	clusters := make([]*models.ClusterConfigExternal, 0)
+//
+// It accepts a list of cluster names that the user wishes to connect to
+func (k *KubeConfig) ToClusterConfigs(allowedClusters []string) []models.ClusterConfig {
+	clusters := make([]models.ClusterConfig, 0)
 
 	// convert clusters, contexts, and users to maps for fast lookup
 	clusterMap := k.createClusterMap()
 	contextMap := k.createContextMap()
 	userMap := k.createUserMap()
 
+	// put allowed clusters in map
+	aClusterMap := createAllowedClusterMap(allowedClusters)
+
 	// iterate through context maps and link to a user-cluster pair
 	for contextName, context := range contextMap {
 		userName := context.Context.User
@@ -49,8 +54,11 @@ func (k *KubeConfig) ToClusterConfigs() []*models.ClusterConfigExternal {
 		_, userFound := userMap[userName]
 		cluster, clusterFound := clusterMap[clusterName]
 
-		if userFound && clusterFound {
-			clusters = append(clusters, &models.ClusterConfigExternal{
+		// make sure the cluster is "allowed"
+		_, aClusterFound := aClusterMap[clusterName]
+
+		if userFound && clusterFound && aClusterFound {
+			clusters = append(clusters, models.ClusterConfig{
 				Name:    clusterName,
 				Server:  cluster.Cluster.Server,
 				Context: contextName,
@@ -62,6 +70,17 @@ func (k *KubeConfig) ToClusterConfigs() []*models.ClusterConfigExternal {
 	return clusters
 }
 
+// createAllowedClusterMap creates a map from a cluster name to a KubeConfigCluster object
+func createAllowedClusterMap(clusters []string) map[string]string {
+	aClusterMap := make(map[string]string)
+
+	for _, cluster := range clusters {
+		aClusterMap[cluster] = cluster
+	}
+
+	return aClusterMap
+}
+
 // createClusterMap creates a map from a cluster name to a KubeConfigCluster object
 func (k *KubeConfig) createClusterMap() map[string]KubeConfigCluster {
 	clusterMap := make(map[string]KubeConfigCluster)

+ 0 - 21
internal/models/user.go

@@ -25,17 +25,6 @@ type UserExternal struct {
 	RawKubeConfig string                   `json:"rawKubeConfig"`
 }
 
-// CreateUserForm represents the accepted values for creating a user
-type CreateUserForm struct {
-	Email    string `json:"email" form:"required,max=255,email"`
-	Password string `json:"password" form:"required,max=255"`
-}
-
-// UpdateUserForm represents the accepted values for updating a user
-type UpdateUserForm struct {
-	RawKubeConfig string `json:"rawKubeConfig"`
-}
-
 // Externalize generates an external User to be shared over REST
 func (u *User) Externalize() *UserExternal {
 	clustersExt := make([]*ClusterConfigExternal, 0)
@@ -51,13 +40,3 @@ func (u *User) Externalize() *UserExternal {
 		RawKubeConfig: string(u.RawKubeConfig),
 	}
 }
-
-// ToUser converts a user form to a user
-//
-// TODO -- PASSWORD HASHING HERE
-func (cuf *CreateUserForm) ToUser() (*User, error) {
-	return &User{
-		Email:    cuf.Email,
-		Password: cuf.Password,
-	}, nil
-}

+ 3 - 3
internal/queries/user.go

@@ -14,10 +14,10 @@ func CreateUser(db *gorm.DB, user *models.User) (*models.User, error) {
 }
 
 // UpdateUser modifies an existing User in the database
-func UpdateUser(db *gorm.DB, user *models.User) error {
+func UpdateUser(db *gorm.DB, user *models.User) (*models.User, error) {
 	if err := db.First(&models.User{}, user.ID).Updates(user).Error; err != nil {
-		return err
+		return nil, err
 	}
 
-	return nil
+	return user, nil
 }

+ 68 - 55
server/api/user_handler.go

@@ -3,86 +3,99 @@ package api
 import (
 	"encoding/json"
 	"net/http"
+	"strconv"
 
-	"github.com/porter-dev/porter/internal/queries"
-
+	"github.com/go-chi/chi"
+	"github.com/porter-dev/porter/internal/forms"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/queries"
+	"gorm.io/gorm"
 )
 
 // HandleCreateUser validates a user form entry, converts the user to a gorm
 // model, and saves the user to the database
 func (app *App) HandleCreateUser(w http.ResponseWriter, r *http.Request) {
-	form := &models.CreateUserForm{}
+	form := &forms.CreateUserForm{}
 
-	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
+	user, err := app.writeUser(form, queries.CreateUser, w, r)
+
+	if err == nil {
+		app.logger.Info().Msgf("New user created: %d", user.ID)
+		w.WriteHeader(http.StatusCreated)
+	}
+}
+
+// HandleReadUser is majestic
+func (app *App) HandleReadUser(w http.ResponseWriter, r *http.Request) {
+	w.WriteHeader(http.StatusOK)
+	w.Write([]byte("{}"))
+}
+
+// HandleUpdateUser validates an update user form entry, updates the user
+// in the database, and writes status accepted
+func (app *App) HandleUpdateUser(w http.ResponseWriter, r *http.Request) {
+	id, err := strconv.ParseUint(chi.URLParam(r, "id"), 0, 64)
+
+	if err != nil || id == 0 {
 		app.handleErrorFormDecoding(err, ErrUserDecode, w)
 		return
 	}
 
+	form := &forms.UpdateUserForm{
+		ID: id,
+	}
+
+	user, err := app.writeUser(form, queries.UpdateUser, w, r)
+
+	if err == nil {
+		app.logger.Info().Msgf("User updated: %d", user.ID)
+		w.WriteHeader(http.StatusAccepted)
+	}
+}
+
+// HandleDeleteUser is majestic
+func (app *App) HandleDeleteUser(w http.ResponseWriter, r *http.Request) {
+	w.WriteHeader(http.StatusAccepted)
+}
+
+// ------------------------ User handler helper functions ------------------------ //
+
+// writeUser will take a POST or PUT request to the /api/users endpoint and decode
+// the request into a forms.WriteUserForm model, convert it to a models.User, and
+// write to the database.
+func (app *App) writeUser(
+	form forms.WriteUserForm,
+	dbWrite func(db *gorm.DB, user *models.User) (*models.User, error),
+	w http.ResponseWriter,
+	r *http.Request,
+) (*models.User, error) {
+	// decode from JSON to form value
+	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
+		app.handleErrorFormDecoding(err, ErrUserDecode, w)
+		return nil, err
+	}
+
+	// validate the form
 	if err := app.validator.Struct(form); err != nil {
 		app.handleErrorFormValidation(err, ErrUserValidateFields, w)
-		return
+		return nil, err
 	}
 
+	// convert the form to a user model -- WriteUserForm must implement ToUser
 	userModel, err := form.ToUser()
 
 	if err != nil {
 		app.handleErrorFormDecoding(err, ErrUserDecode, w)
-		return
+		return nil, err
 	}
 
-	user, err := queries.CreateUser(app.db, userModel)
+	// handle write to the database
+	user, err := dbWrite(app.db, userModel)
 
 	if err != nil {
 		app.handleErrorDataWrite(err, ErrUserDataWrite, w)
-		return
+		return nil, err
 	}
 
-	app.logger.Info().Msgf("New user created: %d", user.ID)
-
-	w.WriteHeader(http.StatusCreated)
+	return user, nil
 }
-
-// HandleReadUser is majestic
-func (app *App) HandleReadUser(w http.ResponseWriter, r *http.Request) {
-	w.WriteHeader(http.StatusOK)
-	w.Write([]byte("{}"))
-}
-
-// HandleUpdateUser is majestic
-func (app *App) HandleUpdateUser(w http.ResponseWriter, r *http.Request) {
-	w.WriteHeader(http.StatusAccepted)
-}
-
-// HandleDeleteUser is majestic
-func (app *App) HandleDeleteUser(w http.ResponseWriter, r *http.Request) {
-	w.WriteHeader(http.StatusAccepted)
-}
-
-// GenerateUser creates a new user based on a unique ID and a kubeconfig
-// func GenerateUser(id string, kubeconfig []byte) *User {
-// 	conf := kubernetes.KubeConfig{}
-
-// 	err := yaml.Unmarshal(kubeconfig, &conf)
-
-// 	// TODO -- HANDLE ERROR
-// 	if err != nil {
-// 		fmt.Println("ERROR IN UNMARSHALING")
-// 	}
-
-// 	// generate the user's clusters
-// 	clusters := conf.ToClusterConfigs()
-
-// 	return &User{
-// 		ID:            id,
-// 		Clusters:      clusters,
-// 		RawKubeConfig: kubeconfig,
-// 	}
-// }
-
-// // printUser is a helper function to print a user's config without sensitive information
-// func (u *User) printUser() {
-// 	for _, cluster := range u.Clusters {
-// 		fmt.Println(cluster.Name, cluster.Context, cluster.Server, cluster.User)
-// 	}
-// }

+ 3 - 1
server/router/router.go

@@ -17,7 +17,9 @@ func New(a *api.App) *chi.Mux {
 
 		// /api/users routes
 		r.Method("POST", "/users", requestlog.NewHandler(a.HandleCreateUser, l))
-		r.Method("GET", "/users", requestlog.NewHandler(a.HandleReadUser, l))
+		r.Method("PUT", "/users/{id}", requestlog.NewHandler(a.HandleUpdateUser, l))
+
+		// r.Method("GET", "/users", requestlog.NewHandler(a.HandleReadUser, l))
 	})
 
 	return r