Ejemplo n.º 1
0
func principalHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	principalName := r.URL.Path[12:]
	switch r.Method {
	case "GET":
		chefActor, err := actor.GetReqUser(principalName)
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusNotFound)
			return
		}
		var chefType string
		if chefActor.IsUser() {
			chefType = "user"
		} else {
			chefType = "client"
		}
		jsonPrincipal := map[string]interface{}{
			"name":       chefActor.GetName(),
			"type":       chefType,
			"public_key": chefActor.PublicKey(),
		}
		enc := json.NewEncoder(w)
		if encerr := enc.Encode(&jsonPrincipal); encerr != nil {
			jsonErrorReport(w, r, encerr.Error(), http.StatusInternalServerError)
			return
		}
	default:
		jsonErrorReport(w, r, "Unrecognized method for principals!", http.StatusMethodNotAllowed)
	}
}
Ejemplo n.º 2
0
func reindexHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	reindexResponse := make(map[string]interface{})
	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 "POST":
		if !opUser.IsAdmin() {
			jsonErrorReport(w, r, "You are not allowed to perform that action.", http.StatusForbidden)
			return
		}
		reindexAll()
		reindexResponse["reindex"] = "OK"
	default:
		jsonErrorReport(w, r, "Method not allowed. If you're trying to do something with a data bag named 'reindex', it's not going to work I'm afraid.", http.StatusMethodNotAllowed)
		return
	}
	enc := json.NewEncoder(w)
	if err := enc.Encode(&reindexResponse); err != nil {
		jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
	}
}
Ejemplo n.º 3
0
// CheckHeader checks the signed headers sent by the client against the expected
// result assembled from the request headers to verify their authorization.
func CheckHeader(userID string, r *http.Request) util.Gerror {
	user, err := actor.GetReqUser(userID)
	if err != nil {
		gerr := util.Errorf("Failed to authenticate as '%s'. Ensure that your node_name and client key are correct.", userID)
		gerr.SetStatus(http.StatusUnauthorized)
		return gerr
	}

	return AuthenticateHeader(user.PublicKey(), config.Config.TimeSlewDur, r)
}
Ejemplo n.º 4
0
// Individual log events
func eventHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}
	eventID, aerr := strconv.Atoi(r.URL.Path[8:])
	if aerr != nil {
		jsonErrorReport(w, r, aerr.Error(), http.StatusBadRequest)
		return
	}

	switch r.Method {
	case "GET":
		if !opUser.IsAdmin() {
			jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden)
			return
		}
		le, err := loginfo.Get(eventID)
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusNotFound)
			return
		}
		enc := json.NewEncoder(w)
		if err = enc.Encode(&le); err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			return
		}
	case "DELETE":
		if !opUser.IsAdmin() {
			jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden)
			return
		}
		le, err := loginfo.Get(eventID)
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusNotFound)
			return
		}
		err = le.Delete()
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			return
		}
		enc := json.NewEncoder(w)
		if err = enc.Encode(&le); err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			return
		}
	default:
		jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	return
}
Ejemplo n.º 5
0
func reportHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	protocolVersion := r.Header.Get("X-Ops-Reporting-Protocol-Version")
	if protocolVersion == "" {
		// try a param (makes working with webui easier)
		form, e := url.ParseQuery(r.URL.RawQuery)
		if e != nil {
			jsonErrorReport(w, r, e.Error(), http.StatusBadRequest)
			return
		}
		if p, f := form["protocol-version"]; f {
			if len(p) > 0 {
				protocolVersion = p[0]
			}
		}
	}
	// someday there may be other protocol versions
	if protocolVersion != "0.1.0" {
		jsonErrorReport(w, r, "Unsupported reporting protocol version", http.StatusNotFound)
		return
	}

	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}

	// TODO: some params for time ranges exist and need to be handled
	// properly

	pathArray := splitPath(r.URL.Path)
	pathArrayLen := len(pathArray)
	reportResponse := make(map[string]interface{})

	switch r.Method {
	case "GET":
		// Making an informed guess that admin rights are needed
		// to see the node run reports
		r.ParseForm()
		var rows int
		var from, until time.Time
		var status string
		if fr, found := r.Form["rows"]; found {
			if len(fr) < 0 {
				jsonErrorReport(w, r, "invalid rows", http.StatusBadRequest)
				return
			}
			var err error
			rows, err = strconv.Atoi(fr[0])
			if err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
				return
			}
		} else {
			// default is 10
			rows = 10
		}
		if ff, found := r.Form["from"]; found {
			if len(ff) < 0 {
				jsonErrorReport(w, r, "invalid from", http.StatusBadRequest)
				return
			}
			fromUnix, err := strconv.ParseInt(ff[0], 10, 64)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
				return
			}
			from = time.Unix(fromUnix, 0)
		} else {
			from = time.Now().Add(-(time.Duration(24*90) * time.Hour))
		}
		if fu, found := r.Form["until"]; found {
			if len(fu) < 0 {
				jsonErrorReport(w, r, "invalid until", http.StatusBadRequest)
				return
			}
			untilUnix, err := strconv.ParseInt(fu[0], 10, 64)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
				return
			}
			until = time.Unix(untilUnix, 0)
		} else {
			until = time.Now()
		}

		if st, found := r.Form["status"]; found {
			if len(st) < 0 {
				jsonErrorReport(w, r, "invalid status", http.StatusBadRequest)
				return
			}
			status = st[0]
			if status != "started" && status != "success" && status != "failure" {
				jsonErrorReport(w, r, "invalid status given", http.StatusBadRequest)
				return
			}
		}

		// If the end time is more than 90 days ahead of the
		// start time, give an error
		if from.Truncate(time.Hour).Sub(until.Truncate(time.Hour)) >= (time.Duration(24*90) * time.Hour) {
			msg := fmt.Sprintf("End time %s is too far ahead of start time %s (max 90 days)", until.String(), from.String())
			jsonErrorReport(w, r, msg, http.StatusNotAcceptable)
			return
		}

		if !opUser.IsAdmin() {
			jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
			return
		}
		if pathArrayLen < 3 || pathArrayLen > 4 {
			jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
			return
		}
		op := pathArray[1]
		if op == "nodes" && pathArrayLen == 4 {
			nodeName := pathArray[2]
			runs, nerr := report.GetNodeList(nodeName, from, until, rows, status)
			if nerr != nil {
				jsonErrorReport(w, r, nerr.Error(), http.StatusInternalServerError)
				return
			}
			reportResponse["run_history"] = runs
		} else if op == "org" {
			if pathArrayLen == 4 {
				runID := pathArray[3]
				run, err := report.Get(runID)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				reportResponse = formatRunShow(run)
			} else {
				runs, rerr := report.GetReportList(from, until, rows, status)
				if rerr != nil {
					jsonErrorReport(w, r, rerr.Error(), http.StatusInternalServerError)
					return
				}
				reportResponse["run_history"] = runs
			}
		} else {
			jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
			return
		}
	case "POST":
		// Can't use the usual parseObjJSON function here, since
		// the reporting "run_list" type is a string rather
		// than []interface{}.
		jsonReport := make(map[string]interface{})
		dec := json.NewDecoder(r.Body)
		if jerr := dec.Decode(&jsonReport); jerr != nil {
			jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest)
			return
		}

		if pathArrayLen < 4 || pathArrayLen > 5 {
			jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
			return
		}
		nodeName := pathArray[2]
		if pathArrayLen == 4 {
			rep, err := report.NewFromJSON(nodeName, jsonReport)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}
			// what's the expected response?
			serr := rep.Save()
			if serr != nil {
				jsonErrorReport(w, r, serr.Error(), http.StatusInternalServerError)
				return
			}
			reportResponse["run_detail"] = rep
		} else {
			runID := pathArray[4]
			rep, err := report.Get(runID)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}
			err = rep.UpdateFromJSON(jsonReport)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}
			serr := rep.Save()
			if serr != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
				return
			}
			// .... and?
			reportResponse["run_detail"] = rep
		}
	default:
		jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
		return
	}

	enc := json.NewEncoder(w)
	if err := enc.Encode(&reportResponse); err != nil {
		jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
	}
}
Ejemplo n.º 6
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
		}
		err = chefNode.UpdateStatus("new")
		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
}
Ejemplo n.º 7
0
func roleHandling(w http.ResponseWriter, r *http.Request) map[string]string {
	roleResponse := 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
		}
		roleList := role.GetList()
		for _, k := range roleList {
			itemURL := fmt.Sprintf("/roles/%s", k)
			roleResponse[k] = util.CustomURL(itemURL)
		}
	case "POST":
		if !opUser.IsAdmin() {
			jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden)
			return nil
		}
		roleData, jerr := parseObjJSON(r.Body)
		if jerr != nil {
			jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest)
			return nil
		}
		if _, ok := roleData["name"].(string); !ok {
			jsonErrorReport(w, r, "Role name missing", http.StatusBadRequest)
			return nil
		}
		chefRole, _ := role.Get(roleData["name"].(string))
		if chefRole != nil {
			httperr := fmt.Errorf("Role already exists")
			jsonErrorReport(w, r, httperr.Error(), http.StatusConflict)
			return nil
		}
		var nerr util.Gerror
		chefRole, nerr = role.NewFromJSON(roleData)
		if nerr != nil {
			jsonErrorReport(w, r, nerr.Error(), nerr.Status())
			return nil
		}
		err := chefRole.Save()
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			return nil
		}
		if lerr := loginfo.LogEvent(opUser, chefRole, "create"); lerr != nil {
			jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
			return nil
		}
		roleResponse["uri"] = util.ObjURL(chefRole)
		w.WriteHeader(http.StatusCreated)
	default:
		jsonErrorReport(w, r, "Method not allowed for roles", http.StatusMethodNotAllowed)
		return nil
	}
	return roleResponse
}
Ejemplo n.º 8
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)
	}
}
Ejemplo n.º 9
0
func cookbookHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	pathArray := splitPath(r.URL.Path)
	cookbookResponse := make(map[string]interface{})

	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}

	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, err.Error(), err.Status())
			return
		}
	}
	force := ""
	if f, fok := r.Form["force"]; fok {
		if len(f) > 0 {
			force = f[0]
		}
	}

	pathArrayLen := len(pathArray)

	/* 1 and 2 length path arrays only support GET */
	if pathArrayLen < 3 && r.Method != "GET" {
		jsonErrorReport(w, r, "Bad request.", http.StatusMethodNotAllowed)
		return
	} else if pathArrayLen < 3 && opUser.IsValidator() {
		jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
		return
	}

	/* chef-pedant is happier when checking if a validator can do something
	 * surprisingly late in the game. It wants the perm checks to be
	 * checked after the method for the end point is checked out as
	 * something it's going to handle, so, for instance, issuing a DELETE
	 * against an endpoint where that isn't allowed should respond with a
	 * 405, rather than a 403, which also makes sense in areas where
	 * validators shouldn't be able to do anything. *shrugs*
	 */

	if pathArrayLen == 1 || (pathArrayLen == 2 && pathArray[1] == "") {
		/* list all cookbooks */
		cookbookResponse = cookbook.CookbookLister(numResults)
	} else if pathArrayLen == 2 {
		/* info about a cookbook and all its versions */
		cookbookName := pathArray[1]
		/* Undocumented behavior - a cookbook name of _latest gets a
		 * list of the latest versions of all the cookbooks, and _recipe
		 * gets the recipes of the latest cookbooks. */
		if cookbookName == "_latest" {
			cookbookResponse = cookbook.CookbookLatest()
		} else if cookbookName == "_recipes" {
			rlist, nerr := cookbook.CookbookRecipes()
			if nerr != nil {
				jsonErrorReport(w, r, nerr.Error(), nerr.Status())
				return
			}
			enc := json.NewEncoder(w)
			if err := enc.Encode(&rlist); err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			}
			return
		} else {
			cb, err := cookbook.Get(cookbookName)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusNotFound)
				return
			}
			/* Strange thing here. The API docs say if num_versions
			 * is not specified to return one cookbook, yet the
			 * spec indicates that if it's not set that all
			 * cookbooks should be returned. Most places *except
			 * here* that's the case, so it can't be changed in
			 * infoHashBase. Explicitly set numResults to all
			 * here. */
			if numResults == "" {
				numResults = "all"
			}
			cookbookResponse[cookbookName] = cb.InfoHash(numResults)
		}
	} else if pathArrayLen == 3 {
		/* get information about or manipulate a specific cookbook
		 * version */
		cookbookName := pathArray[1]
		var cookbookVersion string
		var vererr util.Gerror
		opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
		if oerr != nil {
			jsonErrorReport(w, r, oerr.Error(), oerr.Status())
			return
		}
		if r.Method == "GET" && pathArray[2] == "_latest" { // might be other special vers
			cookbookVersion = pathArray[2]
		} else {
			cookbookVersion, vererr = util.ValidateAsVersion(pathArray[2])
			if vererr != nil {
				vererr := util.Errorf("Invalid cookbook version '%s'.", pathArray[2])
				jsonErrorReport(w, r, vererr.Error(), vererr.Status())
				return
			}
		}
		switch r.Method {
		case "DELETE", "GET":
			if opUser.IsValidator() {
				jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
				return
			}
			cb, err := cookbook.Get(cookbookName)
			if err != nil {
				if err.Status() == http.StatusNotFound {
					msg := fmt.Sprintf("Cannot find a cookbook named %s with version %s", cookbookName, cookbookVersion)
					jsonErrorReport(w, r, msg, err.Status())
				} else {
					jsonErrorReport(w, r, err.Error(), err.Status())
				}
				return
			}
			cbv, err := cb.GetVersion(cookbookVersion)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusNotFound)
				return
			}
			if r.Method == "DELETE" {
				if !opUser.IsAdmin() {
					jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden)
					return
				}
				err := cb.DeleteVersion(cookbookVersion)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				if lerr := loginfo.LogEvent(opUser, cbv, "delete"); lerr != nil {
					jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
					return
				}
				/* If all versions are gone, remove the
				 * cookbook - seems to be the desired
				 * behavior. */
				if cb.NumVersions() == 0 {
					if cerr := cb.Delete(); cerr != nil {
						jsonErrorReport(w, r, cerr.Error(), http.StatusInternalServerError)
						return
					}
				}
			} else {
				/* Special JSON rendition of the
				 * cookbook with some but not all of
				 * the fields. */
				cookbookResponse = cbv.ToJSON(r.Method)
				/* Sometimes, but not always, chef needs
				 * empty slices of maps for these
				 * values. Arrrgh. */
				/* Doing it this way is absolutely
				 * insane. However, webui really wants
				 * this information, while chef-pedant
				 * absolutely does NOT want it there.
				 * knife seems happy without it as well.
				 * Until such time that this gets
				 * resolved in a non-crazy manner, for
				 * this only send that info back if it's
				 * a webui request. */
				if rs := r.Header.Get("X-Ops-Request-Source"); rs == "web" {
					chkDiv := []string{"definitions", "libraries", "attributes", "providers", "resources", "templates", "root_files", "files"}
					for _, cd := range chkDiv {
						if cookbookResponse[cd] == nil {
							cookbookResponse[cd] = make([]map[string]interface{}, 0)
						}
					}
				}
			}
		case "PUT":
			if !opUser.IsAdmin() {
				jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
				return
			}
			cbvData, jerr := parseObjJSON(r.Body)
			if jerr != nil {
				jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest)
				return
			}
			/* First, see if the cookbook already exists, &
			 * if not create it. Second, see if this
			 * specific version of the cookbook exists. If
			 * so, update it, otherwise, create it and set
			 * the latest version as needed. */
			cb, err := cookbook.Get(cookbookName)
			if err != nil {
				cb, err = cookbook.New(cookbookName)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				/* save it so we get the id with mysql
				 * for creating versions & such */
				serr := cb.Save()
				if serr != nil {
					jsonErrorReport(w, r, serr.Error(), http.StatusInternalServerError)
					return
				}
				if lerr := loginfo.LogEvent(opUser, cb, "create"); lerr != nil {
					jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
					return
				}
			}
			cbv, err := cb.GetVersion(cookbookVersion)

			/* Does the cookbook_name in the URL and what's
			 * in the body match? */
			switch t := cbvData["cookbook_name"].(type) {
			case string:
				/* Only send this particular
				 * error if the cookbook version
				 * hasn't been created yet.
				 * Instead we want a slightly
				 * different version later. */
				if t != cookbookName && cbv == nil {
					terr := util.Errorf("Field 'name' invalid")
					jsonErrorReport(w, r, terr.Error(), terr.Status())
					return
				}
			default:
				// rather unlikely, I think, to
				// be able to get here past the
				// cookbook get. Punk out and
				// don't do anything

			}
			if err != nil {
				var nerr util.Gerror
				cbv, nerr = cb.NewVersion(cookbookVersion, cbvData)
				if nerr != nil {
					// If the new version failed to
					// take, and there aren't any
					// other versions of the cookbook
					// it needs to be deleted.
					if cb.NumVersions() == 0 {
						cb.Delete()
					}
					jsonErrorReport(w, r, nerr.Error(), nerr.Status())
					return
				}
				if lerr := loginfo.LogEvent(opUser, cbv, "create"); lerr != nil {
					jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
					return
				}
				w.WriteHeader(http.StatusCreated)
			} else {
				err := cbv.UpdateVersion(cbvData, force)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				gerr := cb.Save()
				if gerr != nil {
					jsonErrorReport(w, r, gerr.Error(), http.StatusInternalServerError)
					return
				}
				if lerr := loginfo.LogEvent(opUser, cbv, "modify"); lerr != nil {
					jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
					return
				}
			}
			/* API docs are wrong. The docs claim that this
			 * should have no response body, but in fact it
			 * wants some (not all) of the cookbook version
			 * data. */
			cookbookResponse = cbv.ToJSON(r.Method)
		default:
			jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed)
			return
		}
	} else {
		/* Say what? Bad request. */
		jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
		return
	}

	enc := json.NewEncoder(w)
	if err := enc.Encode(&cookbookResponse); err != nil {
		jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
	}
}
Ejemplo n.º 10
0
// The whole list
func eventListHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}

	// Look for offset and limit parameters
	r.ParseForm()
	var offset, limit, purgeFrom int
	if o, found := r.Form["offset"]; found {
		if len(o) < 0 {
			jsonErrorReport(w, r, "invalid offsets", http.StatusBadRequest)
			return
		}
		var err error
		offset, err = strconv.Atoi(o[0])
		if err != nil {
			jsonErrorReport(w, r, "invalid offset converstion to int", http.StatusBadRequest)
			return
		}
		if offset < 0 {
			jsonErrorReport(w, r, "invalid negative offset value", http.StatusBadRequest)
			return
		}
	} else {
		offset = 0
	}
	var limitFound bool
	if l, found := r.Form["limit"]; found {
		limitFound = true
		if len(l) < 0 {
			jsonErrorReport(w, r, "invalid limit", http.StatusBadRequest)
			return
		}
		var err error
		limit, err = strconv.Atoi(l[0])
		if err != nil {
			jsonErrorReport(w, r, "invalid limit converstion to int", http.StatusBadRequest)
			return
		}
		if limit < 0 {
			jsonErrorReport(w, r, "invalid negative limit value", http.StatusBadRequest)
			return
		}
	}

	if p, found := r.Form["purge"]; found {
		if len(p) < 0 {
			jsonErrorReport(w, r, "invalid purge id", http.StatusBadRequest)
			return
		}
		var err error
		purgeFrom, err = strconv.Atoi(p[0])
		if err != nil {
			jsonErrorReport(w, r, "invalid purge from converstion to int", http.StatusBadRequest)
			return
		}
		if purgeFrom < 0 {
			jsonErrorReport(w, r, "invalid negative purgeFrom value", http.StatusBadRequest)
			return
		}
	}

	paramStrs := []string{"from", "until", "action", "object_type", "object_name", "doer"}
	searchParams := make(map[string]string, 6)

	for _, v := range paramStrs {
		if st, found := r.Form[v]; found {
			if len(st) < 0 {
				jsonErrorReport(w, r, "invalid "+v, http.StatusBadRequest)
				return
			}
			searchParams[v] = st[0]
		}
	}

	switch r.Method {
	case "GET":
		if !opUser.IsAdmin() {
			jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden)
			return
		}
		var leList []*loginfo.LogInfo
		var err error
		if limitFound {
			leList, err = loginfo.GetLogInfos(searchParams, offset, limit)
		} else {
			leList, err = loginfo.GetLogInfos(searchParams, offset)
		}
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
			return
		}
		leResp := make([]map[string]interface{}, len(leList))
		for i, v := range leList {
			leResp[i] = make(map[string]interface{})
			leResp[i]["event"] = v
			leURL := fmt.Sprintf("/events/%d", v.ID)
			leResp[i]["url"] = util.CustomURL(leURL)
		}
		enc := json.NewEncoder(w)
		if err := enc.Encode(&leResp); err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			return
		}
	case "DELETE":
		if !opUser.IsAdmin() {
			jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden)
			return
		}
		purged, err := loginfo.PurgeLogInfos(purgeFrom)
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
		}
		leResp := make(map[string]string)
		leResp["purged"] = fmt.Sprintf("Purged %d logged events", purged)
		enc := json.NewEncoder(w)
		if err := enc.Encode(&leResp); err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			return
		}
	default:
		jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
}
Ejemplo n.º 11
0
func statusHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}
	if !opUser.IsAdmin() {
		jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden)
		return
	}
	pathArray := splitPath(r.URL.Path)

	if len(pathArray) < 3 {
		jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
		return
	}

	var statusResponse interface{}

	switch r.Method {
	case "GET":
		/* pathArray[1] will tell us what operation we're doing */
		switch pathArray[1] {
		// /status/all/nodes
		case "all":
			if len(pathArray) != 3 {
				jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
				return
			}
			if pathArray[2] != "nodes" {
				jsonErrorReport(w, r, "Invalid object to get status for", http.StatusBadRequest)
				return
			}
			nodes := node.AllNodes()
			sr := make([]map[string]string, len(nodes))
			for i, n := range nodes {
				ns, err := n.LatestStatus()
				if err != nil {
					nsbad := make(map[string]string)
					nsbad["node_name"] = n.Name
					nsbad["status"] = "no record"
					sr[i] = nsbad
					continue
				}
				sr[i] = ns.ToJSON()
				nsurl := fmt.Sprintf("/status/node/%s/latest", n.Name)
				sr[i]["url"] = util.CustomURL(nsurl)
			}
			statusResponse = sr
		// /status/node/<nodeName>/(all|latest)
		case "node":
			if len(pathArray) != 4 {
				jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
				return
			}
			nodeName := pathArray[2]
			op := pathArray[3]
			n, gerr := node.Get(nodeName)
			if gerr != nil {
				jsonErrorReport(w, r, gerr.Error(), gerr.Status())
				return
			}
			switch op {
			case "latest":
				ns, err := n.LatestStatus()
				if err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
					return
				}
				statusResponse = ns.ToJSON()
			case "all":
				ns, err := n.AllStatuses()
				if err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
					return
				}
				sr := make([]map[string]string, len(ns))
				for i, v := range ns {
					sr[i] = v.ToJSON()
				}
				statusResponse = sr
			default:
				jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
				return
			}
		default:
			jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
			return
		}
	default:
		jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	enc := json.NewEncoder(w)
	if err := enc.Encode(&statusResponse); err != nil {
		jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
	}
}
Ejemplo n.º 12
0
func searchHandler(w http.ResponseWriter, r *http.Request) {
	/* ... and we need search to run the environment tests, so here we
	 * go. */
	w.Header().Set("Content-Type", "application/json")
	searchResponse := make(map[string]interface{})
	pathArray := splitPath(r.URL.Path)
	pathArrayLen := len(pathArray)

	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}

	/* set up query params for searching */
	var (
		paramQuery string
		paramsRows int
		sortOrder  string
		start      int
	)
	r.ParseForm()
	if q, found := r.Form["q"]; found {
		if len(q) < 0 {
			jsonErrorReport(w, r, "No query string specified for search", http.StatusBadRequest)
			return
		}
		paramQuery = q[0]
	} else if pathArrayLen != 1 {
		/* default to "*:*" for a search term */
		paramQuery = "*:*"
	}
	if pr, found := r.Form["rows"]; found {
		if len(pr) > 0 {
			paramsRows, _ = strconv.Atoi(pr[0])
		}
	} else {
		paramsRows = 1000
	}
	sortOrder = "id ASC"
	if s, found := r.Form["sort"]; found {
		if len(s) > 0 {
			if s[0] != "" {
				sortOrder = s[0]
			}
		} else {
			sortOrder = "id ASC"
		}
	}
	if st, found := r.Form["start"]; found {
		if len(st) > 0 {
			start, _ = strconv.Atoi(st[0])
		}
	} else {
		start = 0
	}

	var searcher search.Searcher
	if config.Config.PgSearch {
		searcher = &search.PostgresSearch{}
	} else {
		searcher = &search.TrieSearch{}
	}

	if pathArrayLen == 1 {
		/* base end points */
		switch r.Method {
		case "GET":
			if opUser.IsValidator() {
				jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
				return
			}
			searchEndpoints := searcher.GetEndpoints()
			for _, s := range searchEndpoints {
				searchResponse[s] = util.CustomURL(fmt.Sprintf("/search/%s", s))
			}
		default:
			jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}
	} else if pathArrayLen == 2 {
		switch r.Method {
		case "GET", "POST":
			if opUser.IsValidator() {
				jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
				return
			}
			var qerr error
			paramQuery, qerr = url.QueryUnescape(paramQuery)
			if qerr != nil {
				jsonErrorReport(w, r, qerr.Error(), http.StatusBadRequest)
				return
			}
			/* start figuring out what comes in POSTS now,
			 * so the partial search tests don't complain
			 * anymore. */
			var partialData map[string]interface{}
			if r.Method == "POST" {
				var perr error
				partialData, perr = parseObjJSON(r.Body)
				if perr != nil {
					jsonErrorReport(w, r, perr.Error(), http.StatusBadRequest)
					return
				}
			}

			idx := pathArray[1]
			res, err := searcher.Search(idx, paramQuery, paramsRows, sortOrder, start, partialData)

			if err != nil {
				statusCode := http.StatusBadRequest
				re := regexp.MustCompile(`^I don't know how to search for .*? data objects.`)
				if re.MatchString(err.Error()) {
					statusCode = http.StatusNotFound
				}
				jsonErrorReport(w, r, err.Error(), statusCode)
				return
			}

			searchResponse["total"] = len(res)
			searchResponse["start"] = start
			searchResponse["rows"] = res
		default:
			jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}
	} else {
		/* Say what? Bad request. */
		jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
		return
	}

	enc := json.NewEncoder(w)
	if err := enc.Encode(&searchResponse); err != nil {
		jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
	}
}
Ejemplo n.º 13
0
func (h *interceptHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	/* knife sometimes sends URL paths that start with //. Redirecting
	 * worked for GETs, but since it was breaking POSTs and screwing with
	 * GETs with query params, we just clean up the path and move on. */

	/* log the URL */
	// TODO: set this to verbosity level 4 or so
	logger.Debugf("Serving %s -- %s", r.URL.Path, r.Method)

	if r.Method != "CONNECT" {
		if p := cleanPath(r.URL.Path); p != r.URL.Path {
			r.URL.Path = p
		}
	}

	/* Make configurable, I guess, but Chef wants it to be 1000000 */
	if !strings.HasPrefix(r.URL.Path, "/file_store") && r.ContentLength > config.Config.JSONReqMaxSize {
		logger.Debugf("Content length was too long for %s", r.URL.Path)
		http.Error(w, "Content-length too long!", http.StatusRequestEntityTooLarge)
		// hmm, with 1.5 it gets a broken pipe now if we don't do
		// anything with the body they're trying to send. Try copying it
		// to /dev/null. This seems crazy, but merely closing the body
		// doesn't actually work.
		io.Copy(ioutil.Discard, r.Body)
		r.Body.Close()
		return
	} else if r.ContentLength > config.Config.ObjMaxSize {
		http.Error(w, "Content-length waaaaaay too long!", http.StatusRequestEntityTooLarge)
		return
	}

	w.Header().Set("X-Goiardi", "yes")
	w.Header().Set("X-Goiardi-Version", config.Version)
	w.Header().Set("X-Chef-Version", config.ChefVersion)
	apiInfo := fmt.Sprintf("flavor=osc;version:%s;goiardi=%s", config.ChefVersion, config.Version)
	w.Header().Set("X-Ops-API-Info", apiInfo)

	userID := r.Header.Get("X-OPS-USERID")
	if rs := r.Header.Get("X-Ops-Request-Source"); rs == "web" {
		/* If use-auth is on and disable-webui is on, and this is a
		 * webui connection, it needs to fail. */
		if config.Config.DisableWebUI {
			w.Header().Set("Content-Type", "application/json")
			logger.Warningf("Attempting to log in through webui, but webui is disabled")
			jsonErrorReport(w, r, "invalid action", http.StatusUnauthorized)
			return
		}

		/* Check that the user in question with the web request exists.
		 * If not, fail. */
		if _, uherr := actor.GetReqUser(userID); uherr != nil {
			w.Header().Set("Content-Type", "application/json")
			logger.Warningf("Attempting to use invalid user %s through X-Ops-Request-Source = web", userID)
			jsonErrorReport(w, r, "invalid action", http.StatusUnauthorized)
			return
		}
		userID = "chef-webui"
	}
	/* Only perform the authorization check if that's configured. Bomb with
	 * an error if the check of the headers, timestamps, etc. fails. */
	/* No clue why /principals doesn't require authorization. Hrmph. */
	if config.Config.UseAuth && !strings.HasPrefix(r.URL.Path, "/file_store") && !(strings.HasPrefix(r.URL.Path, "/principals") && r.Method == "GET") {
		herr := authentication.CheckHeader(userID, r)
		if herr != nil {
			w.Header().Set("Content-Type", "application/json")
			logger.Errorf("Authorization failure: %s\n", herr.Error())
			w.Header().Set("Www-Authenticate", `X-Ops-Sign version="1.0" version="1.1" version="1.2"`)
			//http.Error(w, herr.Error(), herr.Status())
			jsonErrorReport(w, r, herr.Error(), herr.Status())
			return
		}
	}

	// Experimental: decompress gzipped requests
	if r.Header.Get("Content-Encoding") == "gzip" {
		reader, err := gzip.NewReader(r.Body)
		if err != nil {
			w.Header().Set("Content-Type", "application/json")
			logger.Errorf("Failure decompressing gzipped request body: %s\n", err.Error())
			jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
			return
		}
		r.Body = reader
	}

	http.DefaultServeMux.ServeHTTP(w, r)
}
Ejemplo n.º 14
0
func roleHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}

	/* Roles are bit weird in that there's /roles/NAME, but also
	 * /roles/NAME/environments and /roles/NAME/environments/NAME, so we'll
	 * split up the whole path to get those values. */

	pathArray := splitPath(r.URL.Path)
	roleName := pathArray[1]

	chefRole, err := role.Get(roleName)
	if err != nil {
		jsonErrorReport(w, r, err.Error(), http.StatusNotFound)
		return
	}

	if len(pathArray) == 2 {
		/* Normal /roles/NAME case */
		switch r.Method {
		case "GET", "DELETE":
			if opUser.IsValidator() || (!opUser.IsAdmin() && r.Method == "DELETE") {
				jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
				return
			}
			enc := json.NewEncoder(w)
			if err = enc.Encode(&chefRole); err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
				return
			}
			if r.Method == "DELETE" {
				err = chefRole.Delete()
				if err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
					return
				}
				if lerr := loginfo.LogEvent(opUser, chefRole, "delete"); lerr != nil {
					jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
					return
				}
			}
		case "PUT":
			if !opUser.IsAdmin() {
				jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
				return
			}
			roleData, jerr := parseObjJSON(r.Body)
			if jerr != nil {
				jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest)
				return
			}
			if _, ok := roleData["name"]; !ok {
				roleData["name"] = roleName
			}
			jsonName, sterr := util.ValidateAsString(roleData["name"])
			if sterr != nil {
				jsonErrorReport(w, r, sterr.Error(), sterr.Status())
				return
			}
			if roleName != roleData["name"].(string) {
				jsonErrorReport(w, r, "Role name mismatch", http.StatusBadRequest)
				return
			}
			if jsonName == "" {
				roleData["name"] = roleName
			}
			nerr := chefRole.UpdateFromJSON(roleData)
			if nerr != nil {
				jsonErrorReport(w, r, nerr.Error(), nerr.Status())
				return
			}

			err = chefRole.Save()
			if err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
				return
			}
			if lerr := loginfo.LogEvent(opUser, chefRole, "modify"); lerr != nil {
				jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
				return
			}
			enc := json.NewEncoder(w)
			if err = enc.Encode(&chefRole); err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			}
		default:
			jsonErrorReport(w, r, "Unrecognized method!", http.StatusMethodNotAllowed)
		}
	} else {
		var environmentName string
		if len(pathArray) == 4 {
			environmentName = pathArray[3]
			if _, err := environment.Get(environmentName); err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusNotFound)
				return
			}
		}
		/* only method for the /roles/NAME/environment stuff is GET */
		switch r.Method {
		case "GET":
			/* If we have an environment name, return the
			 * environment specific run_list. Otherwise,
			 * return the environments we have run lists
			 * for. Always at least return "_default",
			 * which refers to run_list. */
			if opUser.IsValidator() {
				jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
				return
			}

			enc := json.NewEncoder(w)
			if environmentName != "" {
				var runList []string
				if environmentName == "_default" {
					runList = chefRole.RunList
				} else {
					runList = chefRole.EnvRunLists[environmentName]
				}
				resp := make(map[string][]string, 1)
				resp["run_list"] = runList
				if err = enc.Encode(&resp); err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
				}
			} else {
				roleEnvs := make([]string, len(chefRole.EnvRunLists)+1)
				roleEnvs[0] = "_default"
				i := 1
				for k := range chefRole.EnvRunLists {
					roleEnvs[i] = k
					i++
				}
				if err = enc.Encode(&roleEnvs); err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
				}
			}
		default:
			jsonErrorReport(w, r, "Unrecognized method!", http.StatusMethodNotAllowed)
		}
	}
}
Ejemplo n.º 15
0
func shoveyHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}
	if !opUser.IsAdmin() && r.Method != "PUT" {
		jsonErrorReport(w, r, "you cannot perform this action", http.StatusForbidden)
		return
	}

	if !config.Config.UseShovey {
		jsonErrorReport(w, r, "shovey is not enabled", http.StatusPreconditionFailed)
		return
	}

	pathArray := splitPath(r.URL.Path)
	pathArrayLen := len(pathArray)

	if pathArrayLen < 2 || pathArrayLen > 4 || pathArray[1] == "" {
		jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
		return
	}
	op := pathArray[1]

	shoveyResponse := make(map[string]interface{})

	switch op {
	case "jobs":
		switch r.Method {
		case "GET":
			switch pathArrayLen {
			case 4:
				shove, err := shovey.Get(pathArray[2])
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				sj, err := shove.GetRun(pathArray[3])
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				shoveyResponse, err = sj.ToJSON()
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
			case 3:
				shove, err := shovey.Get(pathArray[2])
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				shoveyResponse, err = shove.ToJSON()
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
			default:
				shoveyIDs, err := shovey.AllShoveyIDs()
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				enc := json.NewEncoder(w)
				if jerr := enc.Encode(&shoveyIDs); err != nil {
					jsonErrorReport(w, r, jerr.Error(), http.StatusInternalServerError)
				}
				return
			}
		case "POST":
			if pathArrayLen != 2 {
				jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
				return
			}
			shvData, err := parseObjJSON(r.Body)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
				return
			}
			logger.Debugf("shvData: %v", shvData)
			var quorum string
			var timeout int
			var ok bool
			if quorum, ok = shvData["quorum"].(string); !ok {
				quorum = "100%"
			}
			logger.Debugf("run_timeout is a %T", shvData["run_timeout"])
			if t, ok := shvData["run_timeout"].(float64); !ok {
				timeout = 300
			} else {
				timeout = int(t)
			}
			var nodeNames []string

			if shvNodes, ok := shvData["nodes"].([]interface{}); ok {
				if len(shvNodes) == 0 {
					jsonErrorReport(w, r, "no nodes provided", http.StatusBadRequest)
					return
				}
				nodeNames = make([]string, len(shvNodes))
				for i, v := range shvNodes {
					nodeNames[i] = v.(string)
				}
			} else {
				jsonErrorReport(w, r, "node list not an array", http.StatusBadRequest)
				return
			}

			s, gerr := shovey.New(shvData["command"].(string), timeout, quorum, nodeNames)
			if gerr != nil {
				jsonErrorReport(w, r, gerr.Error(), gerr.Status())
				return
			}
			gerr = s.Start()
			if gerr != nil {
				jsonErrorReport(w, r, gerr.Error(), gerr.Status())
				return
			}

			shoveyResponse["id"] = s.RunID
			shoveyResponse["uri"] = util.CustomURL(fmt.Sprintf("/shovey/jobs/%s", s.RunID))
		case "PUT":
			switch pathArrayLen {
			case 3:
				if pathArray[2] != "cancel" {
					jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
					return
				}
				cancelData, perr := parseObjJSON(r.Body)
				if perr != nil {
					jsonErrorReport(w, r, perr.Error(), http.StatusBadRequest)
					return
				}

				var nodeNames []string
				runID, ok := cancelData["run_id"].(string)
				if !ok {
					jsonErrorReport(w, r, "No shovey run ID provided, or provided id was invalid", http.StatusBadRequest)
					return
				}

				if nn, ok := cancelData["nodes"].([]interface{}); ok {
					for _, v := range nn {
						nodeNames = append(nodeNames, v.(string))
					}
				}
				shove, err := shovey.Get(runID)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				if len(nodeNames) != 0 {
					serr := shove.CancelRuns(nodeNames)
					if serr != nil {
						logger.Debugf("Error cancelling runs: %s", serr.Error())
						jsonErrorReport(w, r, err.Error(), err.Status())
						return
					}
				} else {
					err = shove.Cancel()
					if err != nil {
						jsonErrorReport(w, r, err.Error(), err.Status())
						return
					}
				}
				shoveyResponse, err = shove.ToJSON()
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
			case 4:
				sjData, perr := parseObjJSON(r.Body)
				if perr != nil {
					jsonErrorReport(w, r, perr.Error(), http.StatusBadRequest)
					return
				}
				nodeName := pathArray[3]
				logger.Debugf("sjData: %v", sjData)
				shove, err := shovey.Get(pathArray[2])
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				sj, err := shove.GetRun(nodeName)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				err = sj.UpdateFromJSON(sjData)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), err.Status())
					return
				}
				shoveyResponse["id"] = shove.RunID
				shoveyResponse["node"] = nodeName
				shoveyResponse["response"] = "ok"
			default:
				jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
				return
			}
		default:
			jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed)
			return
		}
	case "stream":
		if pathArrayLen != 4 {
			jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
			return
		}
		switch r.Method {
		case "GET":
			var seq int
			r.ParseForm()
			if s, found := r.Form["sequence"]; found {
				if len(s) < 0 {
					jsonErrorReport(w, r, "invalid sequence", http.StatusBadRequest)
					return
				}
				var err error
				seq, err = strconv.Atoi(s[0])
				if err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
					return
				}
			}
			var outType string
			if o, found := r.Form["output_type"]; found {
				if len(o) < 0 {
					jsonErrorReport(w, r, "invalid output type", http.StatusBadRequest)
					return
				}
				outType = o[0]
				if outType != "stdout" && outType != "stderr" && outType != "both" {
					jsonErrorReport(w, r, "output type must be 'stdout', 'stderr', or 'both'", http.StatusBadRequest)
					return
				}
			} else {
				outType = "stdout"
			}
			shove, err := shovey.Get(pathArray[2])
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}
			sj, err := shove.GetRun(pathArray[3])
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}
			stream, err := sj.GetStreamOutput(outType, seq)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}
			combinedOutput, err := sj.CombineStreamOutput(outType, seq)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}
			shoveyResponse["run_id"] = sj.ShoveyUUID
			shoveyResponse["node_name"] = sj.NodeName
			shoveyResponse["output_type"] = outType
			shoveyResponse["is_last"] = false
			if len(stream) != 0 {
				shoveyResponse["last_seq"] = stream[len(stream)-1].Seq
				shoveyResponse["is_last"] = stream[len(stream)-1].IsLast
			}
			shoveyResponse["output"] = combinedOutput
		case "PUT":
			streamData, serr := parseObjJSON(r.Body)
			logger.Debugf("streamData: %v", streamData)
			if serr != nil {
				jsonErrorReport(w, r, serr.Error(), http.StatusBadRequest)
				return
			}
			shove, err := shovey.Get(pathArray[2])
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}
			sj, err := shove.GetRun(pathArray[3])
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}

			output, ok := streamData["output"].(string)
			if !ok {
				oerr := util.Errorf("invalid output")
				jsonErrorReport(w, r, oerr.Error(), oerr.Status())
				return
			}
			outputType, ok := streamData["output_type"].(string)
			if !ok {
				oerr := util.Errorf("invalid output type")
				jsonErrorReport(w, r, oerr.Error(), oerr.Status())
				return
			}

			isLast, ok := streamData["is_last"].(bool)
			if !ok {
				oerr := util.Errorf("invalid is_last")
				jsonErrorReport(w, r, oerr.Error(), oerr.Status())
				return
			}

			seqFloat, ok := streamData["seq"].(float64)
			if !ok {
				oerr := util.Errorf("invalid seq")
				jsonErrorReport(w, r, oerr.Error(), oerr.Status())
				return
			}
			seq := int(seqFloat)

			err = sj.AddStreamOutput(output, outputType, seq, isLast)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}
			shoveyResponse["response"] = "ok"
		default:
			jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed)
			return
		}

	default:
		jsonErrorReport(w, r, "Unrecognized operation", http.StatusBadRequest)
		return
	}

	enc := json.NewEncoder(w)
	if jerr := enc.Encode(&shoveyResponse); jerr != nil {
		jsonErrorReport(w, r, jerr.Error(), http.StatusInternalServerError)
	}

	return
}
Ejemplo n.º 16
0
func clientHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	path := splitPath(r.URL.Path)
	clientName := 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":
		chefClient, gerr := client.Get(clientName)
		if gerr != nil {
			jsonErrorReport(w, r, gerr.Error(), gerr.Status())
			return
		}
		if !opUser.IsAdmin() && !opUser.IsSelf(chefClient) {
			jsonErrorReport(w, r, "Deleting that client is forbidden", http.StatusForbidden)
			return
		}
		/* Docs were incorrect. It does want the body of the
		 * deleted object. */
		jsonClient := chefClient.ToJSON()

		/* Log the delete event before deleting the client, in
		 * case the client is deleting itself. */
		if lerr := loginfo.LogEvent(opUser, chefClient, "delete"); lerr != nil {
			jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
			return
		}
		err := chefClient.Delete()
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusForbidden)
			return
		}

		enc := json.NewEncoder(w)
		if err = enc.Encode(&jsonClient); err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			return
		}
	case "GET":
		chefClient, gerr := client.Get(clientName)

		if gerr != nil {
			jsonErrorReport(w, r, gerr.Error(), gerr.Status())
			return
		}
		if !opUser.IsAdmin() && !opUser.IsSelf(chefClient) {
			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
		 */
		jsonClient := chefClient.ToJSON()
		enc := json.NewEncoder(w)
		if err := enc.Encode(&jsonClient); err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			return
		}
	case "PUT":
		clientData, jerr := parseObjJSON(r.Body)
		if jerr != nil {
			jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest)
			return
		}
		chefClient, err := client.Get(clientName)
		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(clientData); averr != nil {
			jsonErrorReport(w, r, averr.Error(), averr.Status())
			return
		}

		if !opUser.IsAdmin() && !opUser.IsSelf(chefClient) {
			jsonErrorReport(w, r, "You are not allowed to perform that action.", http.StatusForbidden)
			return
		}
		if !opUser.IsAdmin() {
			var verr util.Gerror
			aerr := opUser.CheckPermEdit(clientData, "admin")
			if !opUser.IsValidator() {
				verr = opUser.CheckPermEdit(clientData, "validator")
			}
			if aerr != nil && verr != nil {
				jsonErrorReport(w, r, "Client can be either an admin or a validator, but not both.", http.StatusBadRequest)
				return
			} else if aerr != nil || verr != nil {
				if aerr == nil {
					aerr = verr
				}
				jsonErrorReport(w, r, aerr.Error(), aerr.Status())
				return
			}
		}

		jsonName, sterr := util.ValidateAsString(clientData["name"])
		if sterr != nil {
			jsonErrorReport(w, r, sterr.Error(), http.StatusBadRequest)
			return
		}

		/* If clientName and clientData["name"] aren't the
		 * same, we're renaming. Check the new name doesn't
		 * already exist. */
		jsonClient := chefClient.ToJSON()
		if clientName != jsonName {
			if lerr := loginfo.LogEvent(opUser, chefClient, "modify"); lerr != nil {
				jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
				return
			}
			err := chefClient.Rename(jsonName)
			if err != nil {
				jsonErrorReport(w, r, err.Error(), err.Status())
				return
			}
			w.WriteHeader(http.StatusCreated)
		}
		if uerr := chefClient.UpdateFromJSON(clientData); uerr != nil {
			jsonErrorReport(w, r, uerr.Error(), uerr.Status())
			return
		}

		if pk, pkfound := clientData["public_key"]; pkfound {
			switch pk := pk.(type) {
			case string:
				if pkok, pkerr := client.ValidatePublicKey(pk); !pkok {
					jsonErrorReport(w, r, pkerr.Error(), http.StatusBadRequest)
					return
				}
				chefClient.SetPublicKey(pk)
				jsonClient["public_key"] = pk
			case nil:
				//show_public_key = false

			default:
				jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
				return
			}
		}

		if p, pfound := clientData["private_key"]; pfound {
			switch p := p.(type) {
			case bool:
				if p {
					var cgerr error
					if jsonClient["private_key"], cgerr = chefClient.GenerateKeys(); cgerr != nil {
						jsonErrorReport(w, r, cgerr.Error(), http.StatusInternalServerError)
						return
					}
					// make sure the json
					// client gets the new
					// public key
					jsonClient["public_key"] = chefClient.PublicKey()
				}
			default:
				jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
				return
			}
		}
		chefClient.Save()
		if lerr := loginfo.LogEvent(opUser, chefClient, "modify"); lerr != nil {
			jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
			return
		}

		enc := json.NewEncoder(w)
		if err := enc.Encode(&jsonClient); err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			return
		}
	default:
		jsonErrorReport(w, r, "Unrecognized method for client!", http.StatusMethodNotAllowed)
	}
}
Ejemplo n.º 17
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 {
			var errMsg string
			// bleh, stupid errors
			if err.Status() == http.StatusNotFound && (op != "recipes" && op != "cookbooks") {
				errMsg = fmt.Sprintf("environment '%s' not found", envName)
			} else {
				errMsg = err.Error()
			}
			jsonErrorReport(w, r, errMsg, err.Status())
			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, derr := cookbook.DependsCookbooks(cbVer["run_list"].([]string), env.CookbookVersions)
			if derr != nil {
				switch derr := derr.(type) {
				case *cookbook.DependsError:
					// In 1.0.0-dev, there's a
					// JSONErrorMapReport function in util.
					// Use that when moving this forward
					errMap := make(map[string][]map[string]interface{})
					errMap["error"] = make([]map[string]interface{}, 1)
					errMap["error"][0] = derr.ErrMap()
					w.WriteHeader(http.StatusPreconditionFailed)
					enc := json.NewEncoder(w)
					if jerr := enc.Encode(&errMap); jerr != nil {
						logger.Errorf(jerr.Error())
					}
				default:
					jsonErrorReport(w, r, derr.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)
	}
}
Ejemplo n.º 18
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
}
Ejemplo n.º 19
0
func dataHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	pathArray := splitPath(r.URL.Path)

	dbResponse := make(map[string]interface{})
	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}

	if len(pathArray) == 1 {
		/* Either a list of data bags, or a POST to create a new one */
		switch r.Method {
		case "GET":
			if opUser.IsValidator() {
				jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
				return
			}
			/* The list */
			dbList := databag.GetList()
			for _, k := range dbList {
				dbResponse[k] = util.CustomURL(fmt.Sprintf("/data/%s", k))
			}
		case "POST":
			if !opUser.IsAdmin() {
				jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
				return
			}
			dbData, jerr := parseObjJSON(r.Body)
			if jerr != nil {
				jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest)
				return
			}
			/* check that the name exists */
			switch t := dbData["name"].(type) {
			case string:
				if t == "" {
					jsonErrorReport(w, r, "Field 'name' missing", http.StatusBadRequest)
					return
				}
			default:
				jsonErrorReport(w, r, "Field 'name' missing", http.StatusBadRequest)
				return
			}
			chefDbag, _ := databag.Get(dbData["name"].(string))
			if chefDbag != nil {
				httperr := fmt.Errorf("Data bag %s already exists.", dbData["name"].(string))
				jsonErrorReport(w, r, httperr.Error(), http.StatusConflict)
				return
			}
			chefDbag, nerr := databag.New(dbData["name"].(string))
			if nerr != nil {
				jsonErrorReport(w, r, nerr.Error(), nerr.Status())
				return
			}
			serr := chefDbag.Save()
			if serr != nil {
				jsonErrorReport(w, r, serr.Error(), http.StatusInternalServerError)
				return
			}
			if lerr := loginfo.LogEvent(opUser, chefDbag, "create"); lerr != nil {
				jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
				return
			}
			dbResponse["uri"] = util.ObjURL(chefDbag)
			w.WriteHeader(http.StatusCreated)
		default:
			/* The chef-pedant spec wants this response for
			 * some reason. Mix it up, I guess. */
			w.Header().Set("Allow", "GET, POST")
			jsonErrorReport(w, r, "GET, POST", http.StatusMethodNotAllowed)
			return
		}
	} else {
		dbName := pathArray[1]

		/* chef-pedant is unhappy about not reporting the HTTP status
		 * as 404 by fetching the data bag before we see if the method
		 * is allowed, so do a quick check for that here. */
		if (len(pathArray) == 2 && r.Method == "PUT") || (len(pathArray) == 3 && r.Method == "POST") {
			var allowed string
			if len(pathArray) == 2 {
				allowed = "GET, POST, DELETE"
			} else {
				allowed = "GET, PUT, DELETE"
			}
			w.Header().Set("Allow", allowed)
			jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}
		if opUser.IsValidator() || (!opUser.IsAdmin() && r.Method != "GET") {
			jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden)
			return
		}
		chefDbag, err := databag.Get(dbName)
		if err != nil {
			var errMsg string
			status := err.Status()
			if r.Method == "POST" {
				/* Posts get a special snowflake message */
				errMsg = fmt.Sprintf("No data bag '%s' could be found. Please create this data bag before adding items to it.", dbName)
			} else {
				if len(pathArray) == 3 {
					/* This is nuts. */
					if r.Method == "DELETE" {
						errMsg = fmt.Sprintf("Cannot load data bag %s item %s", dbName, pathArray[2])
					} else {
						errMsg = fmt.Sprintf("Cannot load data bag item %s for data bag %s", pathArray[2], dbName)
					}
				} else {
					errMsg = err.Error()
				}
			}
			jsonErrorReport(w, r, errMsg, status)
			return
		}
		if len(pathArray) == 2 {
			/* getting list of data bag items and creating data bag
			 * items. */
			switch r.Method {
			case "GET":

				for _, k := range chefDbag.ListDBItems() {
					dbResponse[k] = util.CustomObjURL(chefDbag, k)
				}
			case "DELETE":
				/* The chef API docs don't say anything
				 * about this existing, but it does,
				 * and without it you can't delete data
				 * bags at all. */
				dbResponse["chef_type"] = "data_bag"
				dbResponse["json_class"] = "Chef::DataBag"
				dbResponse["name"] = chefDbag.Name
				err := chefDbag.Delete()
				if err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
					return
				}
				if lerr := loginfo.LogEvent(opUser, chefDbag, "delete"); lerr != nil {
					jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
					return
				}
			case "POST":
				rawData := databag.RawDataBagJSON(r.Body)
				dbitem, nerr := chefDbag.NewDBItem(rawData)
				if nerr != nil {
					jsonErrorReport(w, r, nerr.Error(), nerr.Status())
					return
				}
				if lerr := loginfo.LogEvent(opUser, dbitem, "create"); lerr != nil {
					jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
					return
				}

				/* The data bag return values are all
				 * kinds of weird. Sometimes it sends
				 * just the raw data, sometimes it sends
				 * the whole object, sometimes a special
				 * snowflake version. Ugh. Have to loop
				 * through to avoid updating the pointer
				 * in the cache by just assigning
				 * dbitem.RawData to dbResponse. Urk.
				 */
				for k, v := range dbitem.RawData {
					dbResponse[k] = v
				}
				dbResponse["data_bag"] = dbitem.DataBagName
				dbResponse["chef_type"] = dbitem.ChefType
				w.WriteHeader(http.StatusCreated)
			default:
				w.Header().Set("Allow", "GET, DELETE, POST")
				jsonErrorReport(w, r, "GET, DELETE, POST", http.StatusMethodNotAllowed)
				return
			}
		} else {
			/* getting, editing, and deleting existing data bag items. */
			dbItemName := pathArray[2]
			if _, err := chefDbag.GetDBItem(dbItemName); err != nil {
				var httperr string
				if r.Method != "DELETE" {
					httperr = fmt.Sprintf("Cannot load data bag item %s for data bag %s", dbItemName, chefDbag.Name)
				} else {
					httperr = fmt.Sprintf("Cannot load data bag %s item %s", chefDbag.Name, dbItemName)
				}
				jsonErrorReport(w, r, httperr, http.StatusNotFound)
				return
			}
			switch r.Method {
			case "GET":
				dbi, err := chefDbag.GetDBItem(dbItemName)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
					return
				}
				dbResponse = dbi.RawData
			case "DELETE":
				dbi, err := chefDbag.GetDBItem(dbItemName)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
					return
				}
				/* Gotta short circuit this */
				enc := json.NewEncoder(w)
				if err := enc.Encode(&dbi); err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
					return
				}
				err = chefDbag.DeleteDBItem(dbItemName)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
					return
				}
				if lerr := loginfo.LogEvent(opUser, dbi, "delete"); lerr != nil {
					jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
					return
				}
				return
			case "PUT":
				rawData := databag.RawDataBagJSON(r.Body)
				if rawID, ok := rawData["id"]; ok {
					switch rawID := rawID.(type) {
					case string:
						if rawID != dbItemName {
							jsonErrorReport(w, r, "DataBagItem name mismatch.", http.StatusBadRequest)
							return
						}
					default:
						jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
						return
					}
				}
				dbitem, err := chefDbag.UpdateDBItem(dbItemName, rawData)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
					return
				}
				if lerr := loginfo.LogEvent(opUser, dbitem, "modify"); lerr != nil {
					jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError)
					return
				}
				/* Another weird data bag item response
				 * which isn't at all unusual. */
				for k, v := range dbitem.RawData {
					dbResponse[k] = v
				}
				dbResponse["data_bag"] = dbitem.DataBagName
				dbResponse["chef_type"] = dbitem.ChefType
				dbResponse["id"] = dbItemName
			default:
				w.Header().Set("Allow", "GET, DELETE, PUT")
				jsonErrorReport(w, r, "GET, DELETE, PUT", http.StatusMethodNotAllowed)
				return
			}
		}
	}

	enc := json.NewEncoder(w)
	if err := enc.Encode(&dbResponse); err != nil {
		jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
	}
}
Ejemplo n.º 20
0
func sandboxHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	pathArray := splitPath(r.URL.Path)
	sboxResponse := make(map[string]interface{})
	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 "POST":
		if len(pathArray) != 1 {
			jsonErrorReport(w, r, "Bad request.", http.StatusMethodNotAllowed)
			return
		}
		if !opUser.IsAdmin() {
			jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden)
			return
		}
		jsonReq, jerr := parseObjJSON(r.Body)
		if jerr != nil {
			jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest)
			return
		}
		sboxHash, ok := jsonReq["checksums"].(map[string]interface{})
		if !ok {
			jsonErrorReport(w, r, "Field 'checksums' missing", http.StatusBadRequest)
			return
		} else if len(sboxHash) == 0 {
			jsonErrorReport(w, r, "Bad checksums!", http.StatusBadRequest)
			return
		} else {
			for _, j := range sboxHash {
				if j != nil {
					jsonErrorReport(w, r, "Bad checksums!", http.StatusBadRequest)
					return
				}
			}
		}
		sbox, err := sandbox.New(sboxHash)
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
			return
		}
		err = sbox.Save()
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
			return
		}

		/* If we're here, make the slightly weird response. */
		sboxResponse["uri"] = util.ObjURL(sbox)
		sboxResponse["sandbox_id"] = sbox.ID
		sboxResponse["checksums"] = sbox.UploadChkList()
		w.WriteHeader(http.StatusCreated)
	case "PUT":
		if len(pathArray) != 2 {
			jsonErrorReport(w, r, "Bad request.", http.StatusMethodNotAllowed)
			return
		}
		if !opUser.IsAdmin() {
			jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden)
			return
		}

		sandboxID := pathArray[1]

		jsonReq, jerr := parseObjJSON(r.Body)
		if jerr != nil {
			jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest)
			return
		}
		sboxCommit, ok := jsonReq["is_completed"].(bool)
		if !ok {
			jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
			return
		}

		sbox, err := sandbox.Get(sandboxID)
		if err != nil {
			jsonErrorReport(w, r, err.Error(), http.StatusNotFound)
			return
		}

		if err = sbox.IsComplete(); err == nil {
			sbox.Completed = sboxCommit
			err = sbox.Save()
			if err != nil {
				jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
				return
			}
		} else {
			jsonErrorReport(w, r, err.Error(), http.StatusServiceUnavailable)
			return
		}

		/* The response here is a bit confusing too. The
		 * documented behavior doesn't match with the observed
		 * behavior from chef-zero, and it's not real clear what
		 * it wants from the checksums array. Still, we need to
		 * give it what it wants. Ask about this later. */
		sboxResponse["guid"] = sbox.ID
		sboxResponse["name"] = sbox.ID
		sboxResponse["is_completed"] = sbox.Completed
		sboxResponse["create_time"] = sbox.CreationTime.UTC().Format("2006-01-02T15:04:05+00:00")
		sboxResponse["checksums"] = sbox.Checksums
	default:
		jsonErrorReport(w, r, "Unrecognized method!", http.StatusMethodNotAllowed)
		return
	}
	enc := json.NewEncoder(w)
	if err := enc.Encode(&sboxResponse); err != nil {
		jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
	}
}