// UpdateFromJSON updates a user from a JSON object, carrying out a bunch of // validations inside. func (u *User) UpdateFromJSON(jsonUser map[string]interface{}) util.Gerror { userName, nerr := util.ValidateAsString(jsonUser["name"]) if nerr != nil { return nerr } if u.Username != userName { err := util.Errorf("User name %s and %s from JSON do not match", u.Username, userName) return err } /* Validations. */ /* Invalid top level elements */ validElements := []string{"username", "name", "org_name", "public_key", "private_key", "admin", "password", "email", "salt"} ValidElem: for k := range jsonUser { for _, i := range validElements { if k == i { continue ValidElem } } err := util.Errorf("Invalid key %s in request body", k) return err } var verr util.Gerror // Check the password first. If it's bad, bail before touching anything // else. if passwd, ok := jsonUser["password"]; ok { passwd, verr = util.ValidateAsString(passwd) if verr != nil { return verr } if passwd != "" { verr = u.SetPasswd(passwd.(string)) if verr != nil { return verr } } } if adminVal, ok := jsonUser["admin"]; ok { var ab bool if ab, verr = util.ValidateAsBool(adminVal); verr != nil { // NOTE: may need to tweak this error message depending // if this is a user or a client verr = util.Errorf("Field 'admin' invalid") return verr } else if u.Admin && !ab { if u.isLastAdmin() { verr = util.Errorf("Cannot remove admin status from the last admin") verr.SetStatus(http.StatusForbidden) return verr } } u.Admin = ab } return nil }
// NewFromJSON creates a new node from the uploaded JSON. func NewFromJSON(jsonNode map[string]interface{}) (*Node, util.Gerror) { nodeName, nerr := util.ValidateAsString(jsonNode["name"]) if nerr != nil { return nil, nerr } node, err := New(nodeName) if err != nil { return nil, err } err = node.UpdateFromJSON(jsonNode) if err != nil { return nil, err } return node, nil }
// NewFromJSON builds a new client/user from a json object. func NewFromJSON(jsonActor map[string]interface{}) (*Client, util.Gerror) { actorName, nerr := util.ValidateAsString(jsonActor["name"]) if nerr != nil { return nil, nerr } client, err := New(actorName) if err != nil { return nil, err } err = client.UpdateFromJSON(jsonActor) if err != nil { return nil, err } return client, nil }
// NewFromJSON builds a new user from a JSON object. func NewFromJSON(jsonUser map[string]interface{}) (*User, util.Gerror) { userName, nerr := util.ValidateAsString(jsonUser["name"]) if nerr != nil { return nil, nerr } user, err := New(userName) if err != nil { return nil, err } // check if the password is supplied if this is a user, and fail if // it isn't. if _, ok := jsonUser["password"]; !ok { err := util.Errorf("Field 'password' missing") return nil, err } err = user.UpdateFromJSON(jsonUser) if err != nil { return nil, err } return user, nil }
func userHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") path := splitPath(r.URL.Path) userName := path[1] opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } switch r.Method { case "DELETE": chefUser, err := user.Get(userName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } if !opUser.IsAdmin() && !opUser.IsSelf(chefUser) { jsonErrorReport(w, r, "Deleting that user is forbidden", http.StatusForbidden) return } /* Docs were incorrect. It does want the body of the * deleted object. */ jsonUser := chefUser.ToJSON() /* Log the delete event *before* deleting the user, in * case the user is deleting itself. */ if lerr := loginfo.LogEvent(opUser, chefUser, "delete"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } err = chefUser.Delete() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusForbidden) return } enc := json.NewEncoder(w) if encerr := enc.Encode(&jsonUser); encerr != nil { jsonErrorReport(w, r, encerr.Error(), http.StatusInternalServerError) return } case "GET": chefUser, err := user.Get(userName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } if !opUser.IsAdmin() && !opUser.IsSelf(chefUser) { jsonErrorReport(w, r, "You are not allowed to perform that action.", http.StatusForbidden) return } /* API docs are wrong here re: public_key vs. * certificate. Also orgname (at least w/ open source) * and clientname, and it wants chef_type and * json_class */ jsonUser := chefUser.ToJSON() enc := json.NewEncoder(w) if encerr := enc.Encode(&jsonUser); encerr != nil { jsonErrorReport(w, r, encerr.Error(), http.StatusInternalServerError) return } case "PUT": userData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } chefUser, err := user.Get(userName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } /* Makes chef-pedant happy. I suppose it is, after all, * pedantic. */ if averr := util.CheckAdminPlusValidator(userData); averr != nil { jsonErrorReport(w, r, averr.Error(), averr.Status()) return } if !opUser.IsAdmin() && !opUser.IsSelf(chefUser) { jsonErrorReport(w, r, "You are not allowed to perform that action.", http.StatusForbidden) return } if !opUser.IsAdmin() { aerr := opUser.CheckPermEdit(userData, "admin") if aerr != nil { jsonErrorReport(w, r, aerr.Error(), aerr.Status()) return } } jsonName, sterr := util.ValidateAsString(userData["name"]) if sterr != nil { jsonErrorReport(w, r, sterr.Error(), http.StatusBadRequest) return } /* If userName and userData["name"] aren't the * same, we're renaming. Check the new name doesn't * already exist. */ jsonUser := chefUser.ToJSON() delete(jsonUser, "public_key") if userName != jsonName { err := chefUser.Rename(jsonName) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } w.WriteHeader(http.StatusCreated) } if uerr := chefUser.UpdateFromJSON(userData); uerr != nil { jsonErrorReport(w, r, uerr.Error(), uerr.Status()) return } if pk, pkfound := userData["public_key"]; pkfound { switch pk := pk.(type) { case string: if pkok, pkerr := user.ValidatePublicKey(pk); !pkok { jsonErrorReport(w, r, pkerr.Error(), http.StatusBadRequest) return } chefUser.SetPublicKey(pk) jsonUser["public_key"] = pk case nil: //show_public_key = false default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } } if p, pfound := userData["private_key"]; pfound { switch p := p.(type) { case bool: if p { var perr error if jsonUser["private_key"], perr = chefUser.GenerateKeys(); perr != nil { jsonErrorReport(w, r, perr.Error(), http.StatusInternalServerError) return } // make sure the json // client gets the new // public key jsonUser["public_key"] = chefUser.PublicKey() } default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } } serr := chefUser.Save() if serr != nil { jsonErrorReport(w, r, serr.Error(), serr.Status()) return } if lerr := loginfo.LogEvent(opUser, chefUser, "modify"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } enc := json.NewEncoder(w) if encerr := enc.Encode(&jsonUser); encerr != nil { jsonErrorReport(w, r, encerr.Error(), http.StatusInternalServerError) return } default: jsonErrorReport(w, r, "Unrecognized method for user!", http.StatusMethodNotAllowed) } }
// UpdateVersion updates a specific version of a cookbook. func (cbv *CookbookVersion) UpdateVersion(cbvData map[string]interface{}, force string) util.Gerror { /* Allow force to update a frozen cookbook */ if cbv.IsFrozen == true && force != "true" { err := util.Errorf("The cookbook %s at version %s is frozen. Use the 'force' option to override.", cbv.CookbookName, cbv.Version) err.SetStatus(http.StatusConflict) return err } fhashes := cbv.fileHashes() _, nerr := util.ValidateAsString(cbvData["cookbook_name"]) if nerr != nil { if nerr.Error() == "Field 'name' missing" { nerr = util.Errorf("Field 'cookbook_name' missing") } else { nerr = util.Errorf("Field 'cookbook_name' invalid") } return nerr } /* Validation, validation, all is validation. */ validElements := []string{"cookbook_name", "name", "version", "json_class", "chef_type", "definitions", "libraries", "attributes", "recipes", "providers", "resources", "templates", "root_files", "files", "frozen?", "metadata", "force"} ValidElem: for k := range cbvData { for _, i := range validElements { if k == i { continue ValidElem } } err := util.Errorf("Invalid key %s in request body", k) return err } var verr util.Gerror cbvData["chef_type"], verr = util.ValidateAsFieldString(cbvData["chef_type"]) if verr != nil { if verr.Error() == "Field 'name' nil" { cbvData["chef_type"] = cbv.ChefType } else { verr = util.Errorf("Field 'chef_type' invalid") return verr } } else { // Wait, what was I doing here? // if !util.ValidateEnvName(cbvData["chef_type"].(string)) { if cbvData["chef_type"].(string) != "cookbook_version" { verr = util.Errorf("Field 'chef_type' invalid") return verr } } cbvData["json_class"], verr = util.ValidateAsFieldString(cbvData["json_class"]) if verr != nil { if verr.Error() == "Field 'name' nil" { cbvData["json_class"] = cbv.JSONClass } else { verr = util.Errorf("Field 'json_class' invalid") return verr } } else { if cbvData["json_class"].(string) != "Chef::CookbookVersion" { verr = util.Errorf("Field 'json_class' invalid") return verr } } cbvData["version"], verr = util.ValidateAsVersion(cbvData["version"]) if verr != nil { verr = util.Errorf("Field 'version' invalid") return verr } if cbvData["version"].(string) == "0.0.0" && cbv.Version != "" { cbvData["version"] = cbv.Version } divs := []string{"definitions", "libraries", "attributes", "recipes", "providers", "resources", "templates", "root_files", "files"} for _, d := range divs { cbvData[d], verr = util.ValidateCookbookDivision(d, cbvData[d]) if verr != nil { return verr } } cbvData["metadata"], verr = util.ValidateCookbookMetadata(cbvData["metadata"]) if verr != nil { return verr } cbvData["frozen?"], verr = util.ValidateAsBool(cbvData["frozen?"]) if verr != nil { return verr } /* Basic sanity checking */ if cbvData["cookbook_name"].(string) != cbv.CookbookName { err := util.Errorf("Field 'cookbook_name' invalid") return err } if cbvData["name"].(string) != cbv.Name { err := util.Errorf("Field 'name' invalid") return err } if cbvData["version"].(string) != cbv.Version && cbvData["version"] != "0.0.0" { err := util.Errorf("Field 'version' invalid") return err } /* Update the data */ /* With these next two, should we test for existence before setting? */ cbv.ChefType = cbvData["chef_type"].(string) cbv.JSONClass = cbvData["json_class"].(string) cbv.Definitions = convertToCookbookDiv(cbvData["definitions"]) cbv.Libraries = convertToCookbookDiv(cbvData["libraries"]) cbv.Attributes = convertToCookbookDiv(cbvData["attributes"]) cbv.Recipes = cbvData["recipes"].([]map[string]interface{}) cbv.Providers = convertToCookbookDiv(cbvData["providers"]) cbv.Resources = convertToCookbookDiv(cbvData["resources"]) cbv.Templates = convertToCookbookDiv(cbvData["templates"]) cbv.RootFiles = convertToCookbookDiv(cbvData["root_files"]) cbv.Files = convertToCookbookDiv(cbvData["files"]) if cbv.IsFrozen != true { cbv.IsFrozen = cbvData["frozen?"].(bool) } cbv.Metadata = cbvData["metadata"].(map[string]interface{}) /* If we're using SQL, update this version in the DB. */ if config.UsingDB() { if err := cbv.updateCookbookVersionSQL(); err != nil { return err } } /* Clean cookbook hashes */ if len(fhashes) > 0 { // Get our parent. Bravely assuming that if it exists we exist. cbook, _ := Get(cbv.CookbookName) cbook.Versions[cbv.Version] = cbv cbook.deleteHashes(fhashes) } return nil }
func nodeHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") nodeName := r.URL.Path[7:] opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } /* So, what are we doing? Depends on the HTTP method, of course */ switch r.Method { case "GET", "DELETE": if opUser.IsValidator() || !opUser.IsAdmin() && r.Method == "DELETE" && !(opUser.IsClient() && opUser.(*client.Client).NodeName == nodeName) { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } chefNode, nerr := node.Get(nodeName) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), http.StatusNotFound) return } enc := json.NewEncoder(w) if err := enc.Encode(&chefNode); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if r.Method == "DELETE" { err := chefNode.Delete() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, chefNode, "delete"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } } case "PUT": if !opUser.IsAdmin() && !(opUser.IsClient() && opUser.(*client.Client).NodeName == nodeName) { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } nodeData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } chefNode, kerr := node.Get(nodeName) if kerr != nil { jsonErrorReport(w, r, kerr.Error(), http.StatusNotFound) return } /* If nodeName and nodeData["name"] don't match, we * need to make a new node. Make sure that node doesn't * exist. */ if _, found := nodeData["name"]; !found { nodeData["name"] = nodeName } jsonName, sterr := util.ValidateAsString(nodeData["name"]) if sterr != nil { jsonErrorReport(w, r, sterr.Error(), http.StatusBadRequest) return } if nodeName != jsonName && jsonName != "" { jsonErrorReport(w, r, "Node name mismatch.", http.StatusBadRequest) return } if jsonName == "" { nodeData["name"] = nodeName } nerr := chefNode.UpdateFromJSON(nodeData) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), nerr.Status()) return } err := chefNode.Save() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, chefNode, "modify"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } enc := json.NewEncoder(w) if err = enc.Encode(&chefNode); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } default: jsonErrorReport(w, r, "Unrecognized method!", http.StatusMethodNotAllowed) } }
// UpdateFromJSON updates an existing node with the uploaded JSON. func (n *Node) UpdateFromJSON(jsonNode map[string]interface{}) util.Gerror { /* It's actually totally legitimate to save a node with a different * name than you started with, but we need to get/create a new node for * it is all. */ nodeName, nerr := util.ValidateAsString(jsonNode["name"]) if nerr != nil { return nerr } if n.Name != nodeName { err := util.Errorf("Node name %s and %s from JSON do not match.", n.Name, nodeName) return err } /* Validations */ /* Look for invalid top level elements. *We* don't have to worry about * them, but chef-pedant cares (probably because Chef <=10 stores * json objects directly, dunno about Chef 11). */ validElements := []string{"name", "json_class", "chef_type", "chef_environment", "run_list", "override", "normal", "default", "automatic"} ValidElem: for k := range jsonNode { for _, i := range validElements { if k == i { continue ValidElem } } err := util.Errorf("Invalid key %s in request body", k) return err } var verr util.Gerror jsonNode["run_list"], verr = util.ValidateRunList(jsonNode["run_list"]) if verr != nil { return verr } attrs := []string{"normal", "automatic", "default", "override"} for _, a := range attrs { jsonNode[a], verr = util.ValidateAttributes(a, jsonNode[a]) if verr != nil { return verr } } jsonNode["chef_environment"], verr = util.ValidateAsFieldString(jsonNode["chef_environment"]) if verr != nil { if verr.Error() == "Field 'name' nil" { jsonNode["chef_environment"] = n.ChefEnvironment } else { return verr } } else { if !util.ValidateEnvName(jsonNode["chef_environment"].(string)) { verr = util.Errorf("Field 'chef_environment' invalid") return verr } } jsonNode["json_class"], verr = util.ValidateAsFieldString(jsonNode["json_class"]) if verr != nil { if verr.Error() == "Field 'name' nil" { jsonNode["json_class"] = n.JSONClass } else { return verr } } else { if jsonNode["json_class"].(string) != "Chef::Node" { verr = util.Errorf("Field 'json_class' invalid") return verr } } jsonNode["chef_type"], verr = util.ValidateAsFieldString(jsonNode["chef_type"]) if verr != nil { if verr.Error() == "Field 'name' nil" { jsonNode["chef_type"] = n.ChefType } else { return verr } } else { if jsonNode["chef_type"].(string) != "node" { verr = util.Errorf("Field 'chef_type' invalid") return verr } } /* and setting */ n.ChefEnvironment = jsonNode["chef_environment"].(string) n.ChefType = jsonNode["chef_type"].(string) n.JSONClass = jsonNode["json_class"].(string) n.RunList = jsonNode["run_list"].([]string) n.Normal = jsonNode["normal"].(map[string]interface{}) n.Automatic = jsonNode["automatic"].(map[string]interface{}) n.Default = jsonNode["default"].(map[string]interface{}) n.Override = jsonNode["override"].(map[string]interface{}) return nil }
// UpdateFromJSON updates an existing role with the uploaded JSON. func (r *Role) UpdateFromJSON(jsonRole map[string]interface{}) util.Gerror { /* TODO - this and node.UpdateFromJSON may be generalizeable with * reflect - look into it. */ if r.Name != jsonRole["name"] { err := util.Errorf("Role name %s and %s from JSON do not match.", r.Name, jsonRole["name"]) return err } /* Validations */ /* Look for invalid top level elements. See node/node.go for more * information. */ validElements := []string{"name", "json_class", "chef_type", "run_list", "env_run_lists", "default_attributes", "override_attributes", "description"} ValidElem: for k := range jsonRole { for _, i := range validElements { if k == i { continue ValidElem } } err := util.Errorf("Invalid key %s in request body", k) return err } var verr util.Gerror if jsonRole["run_list"], verr = util.ValidateRunList(jsonRole["run_list"]); verr != nil { return verr } if _, erlExists := jsonRole["env_run_lists"]; erlExists { for k, v := range jsonRole["env_run_lists"].(map[string][]string) { if jsonRole["env_run_lists"].(map[string][]string)[k], verr = util.ValidateRunList(v); verr != nil { return verr } } } else { jsonRole["env_run_lists"] = make(map[string][]string) } attrs := []string{"default_attributes", "override_attributes"} for _, a := range attrs { jsonRole[a], verr = util.ValidateAttributes(a, jsonRole[a]) if verr != nil { return verr } } jsonRole["json_class"], verr = util.ValidateAsFieldString(jsonRole["json_class"]) if verr != nil { if verr.Error() == "Field 'name' nil" { jsonRole["json_class"] = r.JSONClass } else { return verr } } else { if jsonRole["json_class"].(string) != "Chef::Role" { verr = util.Errorf("Field 'json_class' invalid") return verr } } // Roles can be empty, just force it into being a string jsonRole["description"], _ = util.ValidateAsString(jsonRole["description"]) jsonRole["chef_type"], verr = util.ValidateAsFieldString(jsonRole["chef_type"]) if verr != nil { if verr.Error() == "Field 'name' nil" { jsonRole["chef_type"] = r.ChefType } else { return verr } } else { if jsonRole["chef_type"].(string) != "role" { verr = util.Errorf("Field 'chef_type' invalid") return verr } } r.ChefType = jsonRole["chef_type"].(string) r.JSONClass = jsonRole["json_class"].(string) r.Description = jsonRole["description"].(string) r.RunList = jsonRole["run_list"].([]string) r.EnvRunLists = jsonRole["env_run_lists"].(map[string][]string) r.Default = jsonRole["default_attributes"].(map[string]interface{}) r.Override = jsonRole["override_attributes"].(map[string]interface{}) return nil }
func environmentHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") accErr := checkAccept(w, r, "application/json") if accErr != nil { jsonErrorReport(w, r, accErr.Error(), http.StatusNotAcceptable) return } opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } pathArray := splitPath(r.URL.Path) envResponse := make(map[string]interface{}) var numResults string r.ParseForm() if nrs, found := r.Form["num_versions"]; found { if len(nrs) < 0 { jsonErrorReport(w, r, "invalid num_versions", http.StatusBadRequest) return } numResults = nrs[0] err := util.ValidateNumVersions(numResults) if err != nil { jsonErrorReport(w, r, "You have requested an invalid number of versions (x >= 0 || 'all')", err.Status()) return } } pathArrayLen := len(pathArray) if pathArrayLen == 1 { switch r.Method { case "GET": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } envList := environment.GetList() for _, env := range envList { envResponse[env] = util.CustomURL(fmt.Sprintf("/environments/%s", env)) } case "POST": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } envData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } if _, ok := envData["name"].(string); !ok || envData["name"].(string) == "" { jsonErrorReport(w, r, "Environment name missing", http.StatusBadRequest) return } chefEnv, _ := environment.Get(envData["name"].(string)) if chefEnv != nil { httperr := fmt.Errorf("Environment already exists") jsonErrorReport(w, r, httperr.Error(), http.StatusConflict) return } var eerr util.Gerror chefEnv, eerr = environment.NewFromJSON(envData) if eerr != nil { jsonErrorReport(w, r, eerr.Error(), eerr.Status()) return } if err := chefEnv.Save(); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return } if lerr := loginfo.LogEvent(opUser, chefEnv, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } envResponse["uri"] = util.ObjURL(chefEnv) w.WriteHeader(http.StatusCreated) default: jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed) return } } else if pathArrayLen == 2 { /* All of the 2 element operations return the environment * object, so we do the json encoding in this block and return * out. */ envName := pathArray[1] env, err := environment.Get(envName) delEnv := false /* Set this to delete the environment after * sending the json. */ if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } switch r.Method { case "GET", "DELETE": /* We don't actually have to do much here. */ if r.Method == "DELETE" { if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } if envName == "_default" { jsonErrorReport(w, r, "The '_default' environment cannot be modified.", http.StatusMethodNotAllowed) return } delEnv = true } else { if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } } case "PUT": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } envData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } if envData == nil { jsonErrorReport(w, r, "No environment data in body at all!", http.StatusBadRequest) return } if _, ok := envData["name"]; !ok { //envData["name"] = envName jsonErrorReport(w, r, "Environment name missing", http.StatusBadRequest) return } jsonName, sterr := util.ValidateAsString(envData["name"]) if sterr != nil { jsonErrorReport(w, r, sterr.Error(), sterr.Status()) return } else if jsonName == "" { jsonErrorReport(w, r, "Environment name missing", http.StatusBadRequest) return } if envName != envData["name"].(string) { env, err = environment.Get(envData["name"].(string)) if err == nil { jsonErrorReport(w, r, "Environment already exists", http.StatusConflict) return } var eerr util.Gerror env, eerr = environment.NewFromJSON(envData) if eerr != nil { jsonErrorReport(w, r, eerr.Error(), eerr.Status()) return } w.WriteHeader(http.StatusCreated) oldenv, olderr := environment.Get(envName) if olderr == nil { oldenv.Delete() } } else { if jsonName == "" { envData["name"] = envName } if err := env.UpdateFromJSON(envData); err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } } if err := env.Save(); err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } if lerr := loginfo.LogEvent(opUser, env, "modify"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } default: jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed) return } enc := json.NewEncoder(w) if err := enc.Encode(&env); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if delEnv { err := env.Delete() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, env, "delete"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } } return } else if pathArrayLen == 3 { envName := pathArray[1] op := pathArray[2] if op == "cookbook_versions" && r.Method != "POST" || op != "cookbook_versions" && r.Method != "GET" { jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed) return } if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } env, err := environment.Get(envName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } switch op { case "cookbook_versions": /* Chef Server API docs aren't even remotely * right here. What it actually wants is the * usual hash of info for the latest or * constrained version. Weird. */ cbVer, jerr := parseObjJSON(r.Body) if jerr != nil { errmsg := jerr.Error() if !strings.Contains(errmsg, "Field") { errmsg = "invalid JSON" } else { errmsg = jerr.Error() } jsonErrorReport(w, r, errmsg, http.StatusBadRequest) return } if _, ok := cbVer["run_list"]; !ok { jsonErrorReport(w, r, "POSTed JSON badly formed.", http.StatusMethodNotAllowed) return } deps, err := cookbook.DependsCookbooks(cbVer["run_list"].([]string), env.CookbookVersions) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusPreconditionFailed) return } /* Need our own encoding here too. */ enc := json.NewEncoder(w) if err := enc.Encode(&deps); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } return case "cookbooks": envResponse = env.AllCookbookHash(numResults) case "nodes": nodeList, err := node.GetFromEnv(envName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } for _, chefNode := range nodeList { envResponse[chefNode.Name] = util.ObjURL(chefNode) } case "recipes": envRecipes := env.RecipeList() /* And... we have to do our own json response * here. Hmph. */ /* TODO: make the JSON encoding stuff its own * function. Dunno why I never thought of that * before now for this. */ enc := json.NewEncoder(w) if err := enc.Encode(&envRecipes); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } return default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } } else if pathArrayLen == 4 { envName := pathArray[1] /* op is either "cookbooks" or "roles", and opName is the name * of the object op refers to. */ op := pathArray[2] opName := pathArray[3] if r.Method != "GET" { jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed) return } if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } env, err := environment.Get(envName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } /* Biting the bullet and not redirecting this to * /roles/NAME/environments/NAME. The behavior is exactly the * same, but it makes clients and chef-pedant somewhat unhappy * to not have this way available. */ if op == "roles" { role, err := role.Get(opName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } var runList []string if envName == "_default" { runList = role.RunList } else { runList = role.EnvRunLists[envName] } envResponse["run_list"] = runList } else if op == "cookbooks" { cb, err := cookbook.Get(opName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } /* Here and, I think, here only, if num_versions isn't * set it's supposed to return ALL matching versions. * API docs are wrong here. */ if numResults == "" { numResults = "all" } envResponse[opName] = cb.ConstrainedInfoHash(numResults, env.CookbookVersions[opName]) } else { /* Not an op we know. */ jsonErrorReport(w, r, "Bad request - too many elements in path", http.StatusBadRequest) return } } else { /* Bad number of path elements. */ jsonErrorReport(w, r, "Bad request - too many elements in path", http.StatusBadRequest) return } enc := json.NewEncoder(w) if err := enc.Encode(&envResponse); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } }
func nodeHandling(w http.ResponseWriter, r *http.Request) map[string]string { /* We're dealing with nodes, then. */ nodeResponse := make(map[string]string) opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return nil } switch r.Method { case "GET": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return nil } nodeList := node.GetList() for _, k := range nodeList { itemURL := fmt.Sprintf("/nodes/%s", k) nodeResponse[k] = util.CustomURL(itemURL) } case "POST": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return nil } nodeData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return nil } nodeName, sterr := util.ValidateAsString(nodeData["name"]) if sterr != nil { jsonErrorReport(w, r, sterr.Error(), http.StatusBadRequest) return nil } chefNode, _ := node.Get(nodeName) if chefNode != nil { httperr := fmt.Errorf("Node already exists") jsonErrorReport(w, r, httperr.Error(), http.StatusConflict) return nil } var nerr util.Gerror chefNode, nerr = node.NewFromJSON(nodeData) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), nerr.Status()) return nil } err := chefNode.Save() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return nil } if lerr := loginfo.LogEvent(opUser, chefNode, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return nil } nodeResponse["uri"] = util.ObjURL(chefNode) w.WriteHeader(http.StatusCreated) default: jsonErrorReport(w, r, "Method not allowed for nodes", http.StatusMethodNotAllowed) return nil } return nodeResponse }
// user handling func userHandling(w http.ResponseWriter, r *http.Request) map[string]string { userResponse := make(map[string]string) opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return nil } switch r.Method { case "GET": userList := user.GetList() for _, k := range userList { /* Make sure it's a client and not a user. */ itemURL := fmt.Sprintf("/users/%s", k) userResponse[k] = util.CustomURL(itemURL) } case "POST": userData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return nil } if averr := util.CheckAdminPlusValidator(userData); averr != nil { jsonErrorReport(w, r, averr.Error(), averr.Status()) return nil } if !opUser.IsAdmin() && !opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return nil } else if !opUser.IsAdmin() && opUser.IsValidator() { if aerr := opUser.CheckPermEdit(userData, "admin"); aerr != nil { jsonErrorReport(w, r, aerr.Error(), aerr.Status()) return nil } if verr := opUser.CheckPermEdit(userData, "validator"); verr != nil { jsonErrorReport(w, r, verr.Error(), verr.Status()) return nil } } userName, sterr := util.ValidateAsString(userData["name"]) if sterr != nil || userName == "" { err := fmt.Errorf("Field 'name' missing") jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return nil } chefUser, err := user.NewFromJSON(userData) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return nil } if publicKey, pkok := userData["public_key"]; !pkok { var perr error if userResponse["private_key"], perr = chefUser.GenerateKeys(); perr != nil { jsonErrorReport(w, r, perr.Error(), http.StatusInternalServerError) return nil } } else { switch publicKey := publicKey.(type) { case string: if pkok, pkerr := user.ValidatePublicKey(publicKey); !pkok { jsonErrorReport(w, r, pkerr.Error(), pkerr.Status()) return nil } chefUser.SetPublicKey(publicKey) case nil: var perr error if userResponse["private_key"], perr = chefUser.GenerateKeys(); perr != nil { jsonErrorReport(w, r, perr.Error(), http.StatusInternalServerError) return nil } default: jsonErrorReport(w, r, "Bad public key", http.StatusBadRequest) return nil } } /* If we make it here, we want the public key in the * response. I think. */ userResponse["public_key"] = chefUser.PublicKey() chefUser.Save() if lerr := loginfo.LogEvent(opUser, chefUser, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return nil } userResponse["uri"] = util.ObjURL(chefUser) w.WriteHeader(http.StatusCreated) default: jsonErrorReport(w, r, "Method not allowed for clients or users", http.StatusMethodNotAllowed) return nil } return userResponse }
// UpdateFromJSON updates an existing environment from JSON uploaded to the // server. func (e *ChefEnvironment) UpdateFromJSON(jsonEnv map[string]interface{}) util.Gerror { if e.Name != jsonEnv["name"].(string) { err := util.Errorf("Environment name %s and %s from JSON do not match", e.Name, jsonEnv["name"].(string)) return err } else if e.Name == "_default" { err := util.Errorf("The '_default' environment cannot be modified.") err.SetStatus(http.StatusMethodNotAllowed) return err } /* Validations */ validElements := []string{"name", "chef_type", "json_class", "description", "default_attributes", "override_attributes", "cookbook_versions"} ValidElem: for k := range jsonEnv { for _, i := range validElements { if k == i { continue ValidElem } } err := util.Errorf("Invalid key %s in request body", k) return err } var verr util.Gerror attrs := []string{"default_attributes", "override_attributes"} for _, a := range attrs { jsonEnv[a], verr = util.ValidateAttributes(a, jsonEnv[a]) if verr != nil { return verr } } jsonEnv["json_class"], verr = util.ValidateAsFieldString(jsonEnv["json_class"]) if verr != nil { if verr.Error() == "Field 'name' nil" { jsonEnv["json_class"] = e.JSONClass } else { return verr } } else { if jsonEnv["json_class"].(string) != "Chef::Environment" { verr = util.Errorf("Field 'json_class' invalid") return verr } } jsonEnv["chef_type"], verr = util.ValidateAsFieldString(jsonEnv["chef_type"]) if verr != nil { if verr.Error() == "Field 'name' nil" { jsonEnv["chef_type"] = e.ChefType } else { return verr } } else { if jsonEnv["chef_type"].(string) != "environment" { verr = util.Errorf("Field 'chef_type' invalid") return verr } } jsonEnv["cookbook_versions"], verr = util.ValidateAttributes("cookbook_versions", jsonEnv["cookbook_versions"]) if verr != nil { return verr } for k, v := range jsonEnv["cookbook_versions"].(map[string]interface{}) { if !util.ValidateEnvName(k) || k == "" { merr := util.Errorf("Cookbook name %s invalid", k) merr.SetStatus(http.StatusBadRequest) return merr } if v == nil { verr = util.Errorf("Invalid version number") return verr } _, verr = util.ValidateAsConstraint(v) if verr != nil { /* try validating as a version */ v, verr = util.ValidateAsVersion(v) if verr != nil { return verr } } } jsonEnv["description"], verr = util.ValidateAsString(jsonEnv["description"]) if verr != nil { if verr.Error() == "Field 'name' missing" { jsonEnv["description"] = "" } else { return verr } } e.ChefType = jsonEnv["chef_type"].(string) e.JSONClass = jsonEnv["json_class"].(string) e.Description = jsonEnv["description"].(string) e.Default = jsonEnv["default_attributes"].(map[string]interface{}) e.Override = jsonEnv["override_attributes"].(map[string]interface{}) /* clear out, then loop over the cookbook versions */ e.CookbookVersions = make(map[string]string, len(jsonEnv["cookbook_versions"].(map[string]interface{}))) for c, v := range jsonEnv["cookbook_versions"].(map[string]interface{}) { e.CookbookVersions[c] = v.(string) } return nil }
// UpdateFromJSON updates a client/user from a json object. Does a bunch of // validations inside rather than in the handler. func (c *Client) UpdateFromJSON(jsonActor map[string]interface{}) util.Gerror { actorName, nerr := util.ValidateAsString(jsonActor["name"]) if nerr != nil { return nerr } if c.Name != actorName { err := util.Errorf("Client name %s and %s from JSON do not match", c.Name, actorName) return err } /* Validations. */ /* Invalid top level elements */ validElements := []string{"name", "json_class", "chef_type", "validator", "org_name", "orgname", "public_key", "private_key", "admin", "certificate", "password", "node_name"} ValidElem: for k := range jsonActor { for _, i := range validElements { if k == i { continue ValidElem } } err := util.Errorf("Invalid key %s in request body", k) return err } var verr util.Gerror jsonActor["json_class"], verr = util.ValidateAsFieldString(jsonActor["json_class"]) if verr != nil { if verr.Error() == "Field 'name' nil" { jsonActor["json_class"] = c.JSONClass } else { return verr } } else { if jsonActor["json_class"].(string) != "Chef::ApiClient" { verr = util.Errorf("Field 'json_class' invalid") return verr } } jsonActor["chef_type"], verr = util.ValidateAsFieldString(jsonActor["chef_type"]) if verr != nil { if verr.Error() == "Field 'name' nil" { jsonActor["chef_type"] = c.ChefType } else { return verr } } else { if jsonActor["chef_type"].(string) != "client" { verr = util.Errorf("Field 'chef_type' invalid") return verr } } var ab, vb bool if adminVal, ok := jsonActor["admin"]; ok { if ab, verr = util.ValidateAsBool(adminVal); verr != nil { // NOTE: may need to tweak this error message depending // if this is a user or a client verr = util.Errorf("Field 'admin' invalid") return verr } else if c.Admin && !ab { if c.isLastAdmin() { verr = util.Errorf("Cannot remove admin status from the last admin") verr.SetStatus(http.StatusForbidden) return verr } } } if validatorVal, ok := jsonActor["validator"]; ok { if vb, verr = util.ValidateAsBool(validatorVal); verr != nil { return verr } } if ab && vb { verr = util.Errorf("Client can be either an admin or a validator, but not both.") verr.SetStatus(http.StatusBadRequest) return verr } c.Admin = ab c.Validator = vb c.ChefType = jsonActor["chef_type"].(string) c.JSONClass = jsonActor["json_class"].(string) return nil }