Beispiel #1
0
// 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
}
Beispiel #2
0
// 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
}
Beispiel #3
0
// 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
}
Beispiel #4
0
// 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
}
Beispiel #5
0
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)
	}
}
Beispiel #6
0
// 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
}
Beispiel #7
0
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)
	}
}
Beispiel #8
0
// 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
}
Beispiel #9
0
// 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
}
Beispiel #10
0
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)
	}
}
Beispiel #11
0
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
}
Beispiel #12
0
// 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
}
Beispiel #13
0
// 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
}
Beispiel #14
0
// 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
}