Example #1
0
func parseAclRequestTyped(cx *goweb.Context) (ids []string, err error) {
	var users []string
	query := util.Q(cx.Request.URL.Query())
	params, _, err := request.ParseMultipartForm(cx.Request)
	if err != nil && err.Error() == "request Content-Type isn't multipart/form-data" && query.Has("users") {
		users = strings.Split(query.Value("users"), ",")
	} else if params["users"] != "" {
		users = strings.Split(params["users"], ",")
	} else {
		return nil, errors.New("Action requires list of comma seperated email address in 'users' parameter")
	}
	for _, v := range users {
		if isEmail(v) {
			u := user.User{Username: v, Email: v}
			if err := u.SetUuid(); err != nil {
				return nil, err
			}
			ids = append(ids, u.Uuid)
		} else if isUuid(v) {
			ids = append(ids, v)
		} else {
			return nil, errors.New("Unknown user id. Must be uuid or email address")
		}
	}
	return ids, nil
}
Example #2
0
func parseAclRequest(cx *goweb.Context) (ids map[string][]string, err error) {
	ids = map[string][]string{}
	users := map[string][]string{}
	query := util.Q(cx.Request.URL.Query())
	params, _, err := request.ParseMultipartForm(cx.Request)
	if err != nil && err.Error() == "request Content-Type isn't multipart/form-data" && (query.Has("all") || query.Has("read") || query.Has("write") || query.Has("delete")) {
		if query.Has("all") {
			users["all"] = strings.Split(query.Value("all"), ",")
		}
		if query.Has("read") {
			users["read"] = strings.Split(query.Value("read"), ",")
		}
		if query.Has("write") {
			users["write"] = strings.Split(query.Value("write"), ",")
		}
		if query.Has("delete") {
			users["delete"] = strings.Split(query.Value("delete"), ",")
		}
	} else if params["all"] != "" || params["read"] != "" || params["write"] != "" || params["delete"] != "" {
		users["all"] = strings.Split(params["all"], ",")
		users["read"] = strings.Split(params["read"], ",")
		users["write"] = strings.Split(params["write"], ",")
		users["delete"] = strings.Split(params["delete"], ",")
	} else {
		return nil, errors.New("Action requires list of comma seperated email address in 'all', 'read', 'write', and/or 'delete' parameter")
	}
	for k, _ := range users {
		for _, v := range users[k] {
			if v != "" {
				if isEmail(v) {
					u := user.User{Username: v, Email: v}
					if err := u.SetUuid(); err != nil {
						return nil, err
					}
					ids[k] = append(ids[k], u.Uuid)
				} else if isUuid(v) {
					ids[k] = append(ids[k], v)
				} else {
					return nil, errors.New("Unknown user id. Must be uuid or email address")
				}
			}
		}
	}
	if len(ids["all"]) > 0 {
		ids["read"] = append(ids["read"], ids["all"]...)
		ids["write"] = append(ids["write"], ids["all"]...)
		ids["delete"] = append(ids["delete"], ids["all"]...)
	}
	delete(ids, "all")
	return ids, nil
}
Example #3
0
File: acl.go Project: MG-RAST/Shock
func parseAclRequestTyped(ctx context.Context) (ids []string, err error) {
	var users []string
	query := ctx.HttpRequest().URL.Query()
	params, _, err := request.ParseMultipartForm(ctx.HttpRequest())
	if _, ok := query["users"]; ok && err != nil && err.Error() == "request Content-Type isn't multipart/form-data" {
		users = strings.Split(query.Get("users"), ",")
	} else if params["users"] != "" {
		users = strings.Split(params["users"], ",")
	} else {
		return nil, errors.New("Action requires list of comma separated usernames in 'users' parameter")
	}
	for _, v := range users {
		if uuid.Parse(v) != nil {
			ids = append(ids, v)
		} else {
			u := user.User{Username: v}
			if err := u.SetMongoInfo(); err != nil {
				return nil, err
			}
			ids = append(ids, u.Uuid)
		}
	}
	return ids, nil
}
Example #4
0
// GET: /node
// To do:
// - Iterate node queries
func (cr *NodeController) ReadMany(ctx context.Context) error {
	u, err := request.Authenticate(ctx.HttpRequest())
	if err != nil && err.Error() != e.NoAuth {
		return request.AuthError(err, ctx)
	}

	// Gather query params
	query := ctx.HttpRequest().URL.Query()

	// Setup query and nodes objects
	q := bson.M{}
	nodes := node.Nodes{}

	if u != nil {
		// Admin sees all
		if !u.Admin {
			q["$or"] = []bson.M{bson.M{"acl.read": []string{}}, bson.M{"acl.read": u.Uuid}, bson.M{"acl.owner": u.Uuid}}
		}
	} else {
		if conf.Bool(conf.Conf["anon-read"]) {
			// select on only nodes with no read rights set
			q["acl.read"] = []string{}
		} else {
			return responder.RespondWithError(ctx, http.StatusUnauthorized, e.NoAuth)
		}
	}

	// Gather params to make db query. Do not include the
	// following list.
	paramlist := map[string]int{"limit": 1, "offset": 1, "query": 1, "querynode": 1, "owner": 1, "read": 1, "write": 1, "delete": 1, "public_owner": 1, "public_read": 1, "public_write": 1, "public_delete": 1}
	if _, ok := query["query"]; ok {
		for key := range query {
			if _, found := paramlist[key]; !found {
				keyStr := fmt.Sprintf("attributes.%s", key)
				value := query.Get(key)
				if value != "" {
					if numValue, err := strconv.Atoi(value); err == nil {
						q["$or"] = ListOfMaps{{keyStr: value}, {keyStr: numValue}}
					} else if value == "null" {
						q["$or"] = ListOfMaps{{keyStr: value}, {keyStr: nil}}
					} else {
						q[keyStr] = value
					}
				} else {
					existsMap := map[string]bool{
						"$exists": true,
					}
					q[keyStr] = existsMap
				}
			}
		}
	} else if _, ok := query["querynode"]; ok {
		for key := range query {
			if _, found := paramlist[key]; !found {
				value := query.Get(key)
				if value != "" {
					if numValue, err := strconv.Atoi(value); err == nil {
						q["$or"] = ListOfMaps{{key: value}, {key: numValue}}
					} else if value == "null" {
						q["$or"] = ListOfMaps{{key: value}, {key: nil}}
					} else {
						q[key] = value
					}
				} else {
					existsMap := map[string]bool{
						"$exists": true,
					}
					q[key] = existsMap
				}
			}
		}
	}

	// defaults
	limit := 25
	offset := 0
	if _, ok := query["limit"]; ok {
		limit = util.ToInt(query.Get("limit"))
	}
	if _, ok := query["offset"]; ok {
		offset = util.ToInt(query.Get("offset"))
	}

	// Allowing user to query based on ACL's with a comma-separated list of users.
	// Users can be written as a username or a UUID.
	for _, atype := range []string{"owner", "read", "write", "delete"} {
		if _, ok := query[atype]; ok {
			users := strings.Split(query.Get(atype), ",")
			for _, v := range users {
				if uuid.Parse(v) != nil {
					q["acl."+atype] = v
				} else {
					u := user.User{Username: v}
					if err := u.SetMongoInfo(); err != nil {
						err_msg := "err " + err.Error()
						logger.Error(err_msg)
						return responder.RespondWithError(ctx, http.StatusBadRequest, err_msg)
					}
					q["acl."+atype] = u.Uuid
				}
			}
		}
	}

	if _, ok := query["public_owner"]; ok {
		// If search is for public_owner AND owner = non-empty string then return zero nodes, no nodes can match this query.
		if _, exists := q["acl.owner"]; exists {
			return responder.RespondWithPaginatedData(ctx, nodes, limit, offset, 0)
		}
		q["acl.owner"] = ""
	}

	// Allowing users to query based on whether ACL is public
	for _, atype := range []string{"read", "write", "delete"} {
		if _, ok := query["public_"+atype]; ok {
			sizeMap := map[string]int{
				"$size": 0,
			}
			// Currently a node cannot be public and have an ACL at the same time, so this returns zero nodes.
			if _, exists := q["acl."+atype]; exists {
				return responder.RespondWithPaginatedData(ctx, nodes, limit, offset, 0)
			} else {
				q["acl."+atype] = sizeMap
			}
		}
	}

	// Get nodes from db
	count, err := nodes.GetPaginated(q, limit, offset)
	if err != nil {
		err_msg := "err " + err.Error()
		logger.Error(err_msg)
		return responder.RespondWithError(ctx, http.StatusBadRequest, err_msg)
	}
	return responder.RespondWithPaginatedData(ctx, nodes, limit, offset, count)
}
Example #5
0
// GET: /node
// To do:
// - Iterate node queries
func (cr *NodeController) ReadMany(ctx context.Context) error {
	u, err := request.Authenticate(ctx.HttpRequest())
	if err != nil && err.Error() != e.NoAuth {
		return request.AuthError(err, ctx)
	}

	// Gather query params
	query := ctx.HttpRequest().URL.Query()

	// Setup query and nodes objects
	// Note: query is composed of 3 sub-query objects:
	// 1) qPerm - user permissions (system-defined)
	// 2) qOpts - query options (user-defined)
	// 3) qAcls - ACL queries (user-defined)
	q := bson.M{}
	qPerm := bson.M{}
	qOpts := bson.M{}
	qAcls := bson.M{}
	nodes := node.Nodes{}

	if u != nil {
		// Skip this part if user is an admin
		if !u.Admin {
			qPerm["$or"] = []bson.M{bson.M{"acl.read": "public"}, bson.M{"acl.read": u.Uuid}, bson.M{"acl.owner": u.Uuid}}
		}
	} else {
		// User is anonymous
		if conf.ANON_READ {
			// select on only nodes that are publicly readable
			qPerm["acl.read"] = "public"
		} else {
			return responder.RespondWithError(ctx, http.StatusUnauthorized, e.NoAuth)
		}
	}

	// bson.M is a convenient alias for a map[string]interface{} map, useful for dealing with BSON in a native way.
	var OptsMArray []bson.M

	// Gather params to make db query. Do not include the following list.
	if _, ok := query["query"]; ok {
		paramlist := map[string]int{"query": 1, "limit": 1, "offset": 1, "order": 1, "direction": 1, "distinct": 1}
		for key := range query {
			if _, found := paramlist[key]; !found {
				keyStr := fmt.Sprintf("attributes.%s", key)
				for _, value := range query[key] {
					if value != "" {
						OptsMArray = append(OptsMArray, parseOption(keyStr, value))
					} else {
						OptsMArray = append(OptsMArray, bson.M{keyStr: map[string]bool{"$exists": true}})
					}
				}
			}
		}
	} else if _, ok := query["querynode"]; ok {
		paramlist := map[string]int{"querynode": 1, "limit": 1, "offset": 1, "order": 1, "direction": 1, "distinct": 1, "owner": 1, "read": 1, "write": 1, "delete": 1, "public_owner": 1, "public_read": 1, "public_write": 1, "public_delete": 1}
		for key := range query {
			if _, found := paramlist[key]; !found {
				for _, value := range query[key] {
					if value != "" {
						OptsMArray = append(OptsMArray, parseOption(key, value))
					} else {
						OptsMArray = append(OptsMArray, bson.M{key: map[string]bool{"$exists": true}})
					}
				}
			}
		}
	}

	if len(OptsMArray) > 0 {
		qOpts["$and"] = OptsMArray
	}

	// bson.M is a convenient alias for a map[string]interface{} map, useful for dealing with BSON in a native way.
	var AclsMArray []bson.M

	// Allowing user to query based on ACL's with a comma-separated list of users.
	// Restricting ACL queries to just the querynode operation.
	// Users can be written as a username or a UUID.
	if _, qok := query["querynode"]; qok {
		for _, atype := range []string{"owner", "read", "write", "delete"} {
			if _, ok := query[atype]; ok {
				users := strings.Split(query.Get(atype), ",")
				for _, v := range users {
					if uuid.Parse(v) != nil {
						AclsMArray = append(AclsMArray, bson.M{"acl." + atype: v})
					} else {
						u := user.User{Username: v}
						if err := u.SetMongoInfo(); err != nil {
							err_msg := "err " + err.Error()
							logger.Error(err_msg)
							return responder.RespondWithError(ctx, http.StatusBadRequest, err_msg)
						}
						AclsMArray = append(AclsMArray, bson.M{"acl." + atype: u.Uuid})
					}
				}
			}
		}
		// Allowing users to query based on whether ACL is public
		for _, atype := range []string{"owner", "read", "write", "delete"} {
			if _, ok := query["public_"+atype]; ok {
				AclsMArray = append(AclsMArray, bson.M{"acl." + atype: "public"})
			}
		}
	}

	if len(AclsMArray) > 0 {
		qAcls["$and"] = AclsMArray
	}

	// Combine permissions query with query parameters and ACL query into one AND clause
	q["$and"] = []bson.M{qPerm, qOpts, qAcls}

	// process distinct query
	if _, ok := query["distinct"]; ok {
		dField := query.Get("distinct")
		if !node.HasAttributeField(dField) {
			err_msg := "err unable to run distinct query on non-indexed attributes field: " + dField
			logger.Error(err_msg)
			return responder.RespondWithError(ctx, http.StatusBadRequest, err_msg)
		}
		results, err := node.DbFindDistinct(q, dField)
		if err != nil {
			err_msg := "err " + err.Error()
			logger.Error(err_msg)
			return responder.RespondWithError(ctx, http.StatusBadRequest, err_msg)
		}
		return responder.RespondWithData(ctx, results)
	}

	// defaults
	order := "created_on"
	direction := "-"
	limit := 25
	offset := 0

	// get from query
	if _, ok := query["query"]; ok {
		if _, ok := query["order"]; ok {
			order = fmt.Sprintf("attributes.%s", query.Get("order"))
		}
	} else {
		if _, ok := query["order"]; ok {
			order = query.Get("order")
		}
	}
	if _, ok := query["direction"]; ok {
		if query.Get("direction") == "asc" {
			direction = ""
		}
	}
	if _, ok := query["limit"]; ok {
		limit = util.ToInt(query.Get("limit"))
	}
	if _, ok := query["offset"]; ok {
		offset = util.ToInt(query.Get("offset"))
	}

	// Get nodes from db
	order = direction + order
	count, err := nodes.GetPaginated(q, limit, offset, order)
	if err != nil {
		err_msg := "err " + err.Error()
		logger.Error(err_msg)
		return responder.RespondWithError(ctx, http.StatusBadRequest, err_msg)
	}
	return responder.RespondWithPaginatedData(ctx, nodes, limit, offset, count)
}
Example #6
0
File: acl.go Project: paczian/Shock
// GET, POST, PUT, DELETE: /node/{nid}/acl/
// GET is the only action implemented here.
func AclRequest(ctx context.Context) {
	nid := ctx.PathValue("nid")

	u, err := request.Authenticate(ctx.HttpRequest())
	if err != nil && err.Error() != e.NoAuth {
		request.AuthError(err, ctx)
		return
	}

	// acl require auth even for public data
	if u == nil {
		responder.RespondWithError(ctx, http.StatusUnauthorized, e.NoAuth)
		return
	}

	// Load node and handle user unauthorized
	n, err := node.LoadUnauth(nid)
	if err != nil {
		if err.Error() == e.MongoDocNotFound {
			responder.RespondWithError(ctx, http.StatusNotFound, "Node not found")
			return
		} else {
			// In theory the db connection could be lost between
			// checking user and load but seems unlikely.
			err_msg := "Err@node_Read:LoadNode: " + err.Error()
			logger.Error(err_msg)
			responder.RespondWithError(ctx, http.StatusInternalServerError, err_msg)
			return
		}
	}

	// only the owner can view/edit acl's unless owner="" or owner=nil
	// note: owner can only be empty when anonymous node creation is enabled in shock config.
	if n.Acl.Owner != u.Uuid && n.Acl.Owner != "" {
		err_msg := "Only the node owner can edit/view node ACL's"
		logger.Error(err_msg)
		responder.RespondWithError(ctx, http.StatusUnauthorized, err_msg)
		return
	}

	if ctx.HttpRequest().Method == "GET" {
	    if u.Uuid == n.Acl.Owner || rights["read"] {
		// this is pretty clumsy and I could have done it with a lot less code and cleaner
		// in perl or js, but I am not so familiar with GO yet (it always hurts the first time)
		// the return structure should probably not be a concatenated string, but rather a hash
		// for each entry containing Username, Fullname and Uuid
		
		// owner
		cu, err := user.FindByUuid(n.Acl.Owner)
		if err != nil {
		    responder.RespondWithError(ctx, http.StatusInternalServerError, "Err@Uuid_Resolve: "+err.Error())
		    return
		} else {
		    n.Acl.Owner = cu.Username + "|" + cu.Uuid
		}

		// read
		p1 := []string{}
		for _, v := range n.Acl.Read {
		    cu, err := user.FindByUuid(v)
		    if err != nil {
			responder.RespondWithError(ctx, http.StatusInternalServerError, "Err@Uuid_Resolve: "+err.Error())
			return
		    } else {
			p1 = append(p1, cu.Username + "|" + cu.Uuid)
		    }
		}
		n.Acl.Read = p1

		// write
		p2 := []string{}
		for _, v := range n.Acl.Write {
		    cu, err := user.FindByUuid(v)
		    if err != nil {
			responder.RespondWithError(ctx, http.StatusInternalServerError, "Err@Uuid_Resolve: "+err.Error())
			return
		    } else {
			p2 = append(p2, cu.Username + "|" + cu.Uuid)
		    }
		}
		n.Acl.Write = p2

		// delete
		p3 := []string{}
		for _, v := range n.Acl.Delete {
		    cu, err := user.FindByUuid(v)
		    if err != nil {
			responder.RespondWithError(ctx, http.StatusInternalServerError, "Err@Uuid_Resolve: "+err.Error())
			return
		    } else {
			p3 = append(p3, cu.Username + "|" + cu.Uuid)
		    }
		}
		n.Acl.Delete = p3

		responder.RespondWithData(ctx, n.Acl)
	} else {
		responder.RespondWithError(ctx, http.StatusNotImplemented, "This request type is not implemented.")
	}
	return
}

// GET, POST, PUT, DELETE: /node/{nid}/acl/{type}
func AclTypedRequest(ctx context.Context) {
	nid := ctx.PathValue("nid")
	rtype := ctx.PathValue("type")

	u, err := request.Authenticate(ctx.HttpRequest())
	if err != nil && err.Error() != e.NoAuth {
		request.AuthError(err, ctx)
		return
	}

	if !validAclTypes[rtype] {
		responder.RespondWithError(ctx, http.StatusBadRequest, "Invalid acl type")
		return
	}

	// acl require auth even for public data
	if u == nil {
		responder.RespondWithError(ctx, http.StatusUnauthorized, e.NoAuth)
		return
	}

	// Load node and handle user unauthorized
	n, err := node.LoadUnauth(nid)
	if err != nil {
		if err.Error() == e.MongoDocNotFound {
			responder.RespondWithError(ctx, http.StatusNotFound, "Node not found")
			return
		} else {
			// In theory the db connection could be lost between
			// checking user and load but seems unlikely.
			err_msg := "Err@node_Read:LoadNode: " + err.Error()
			logger.Error(err_msg)
			responder.RespondWithError(ctx, http.StatusInternalServerError, err_msg)
			return
		}
	}

	// only the owner can view/edit acl's unless owner="" or owner=nil
	// note: owner can only be empty when anonymous node creation is enabled in shock config.
	if n.Acl.Owner != u.Uuid && n.Acl.Owner != "" {
		err_msg := "Only the node owner can edit/view node ACL's"
		logger.Error(err_msg)
		responder.RespondWithError(ctx, http.StatusBadRequest, err_msg)
		return
	}

	requestMethod := ctx.HttpRequest().Method
	if requestMethod != "GET" {
		ids, err := parseAclRequestTyped(ctx)
		if err != nil {
			responder.RespondWithError(ctx, http.StatusBadRequest, err.Error())
			return
		}
		if requestMethod == "POST" || requestMethod == "PUT" {
			if rtype == "owner" {
				if len(ids) == 1 {
					n.Acl.SetOwner(ids[0])
				} else {
					responder.RespondWithError(ctx, http.StatusBadRequest, "Too many users. Nodes may have only one owner.")
					return
				}
			} else if rtype == "all" {
				for _, atype := range []string{"read", "write", "delete"} {
					for _, i := range ids {
						n.Acl.Set(i, map[string]bool{atype: true})
					}
				}
			} else {
				for _, i := range ids {
					n.Acl.Set(i, map[string]bool{rtype: true})
				}
			}
			n.Save()
		} else if requestMethod == "DELETE" {
			if rtype == "owner" {
				responder.RespondWithError(ctx, http.StatusBadRequest, "Deleting ownership is not a supported request type.")
				return
			} else if rtype == "all" {
				for _, atype := range []string{"read", "write", "delete"} {
					for _, i := range ids {
						n.Acl.UnSet(i, map[string]bool{atype: true})
					}
				}
			} else {
				for _, i := range ids {
					n.Acl.UnSet(i, map[string]bool{rtype: true})
				}
			}
			n.Save()
		} else {
			responder.RespondWithError(ctx, http.StatusNotImplemented, "This request type is not implemented.")
			return
		}
	}

	switch rtype {
	default:
		responder.RespondWithError(ctx, http.StatusNotImplemented, "This request type is not implemented.")
	case "read":
		responder.RespondWithData(ctx, map[string][]string{"read": n.Acl.Read})
	case "write":
		responder.RespondWithData(ctx, map[string][]string{"write": n.Acl.Write})
	case "delete":
		responder.RespondWithData(ctx, map[string][]string{"delete": n.Acl.Delete})
	case "owner":
		responder.RespondWithData(ctx, map[string]string{"owner": n.Acl.Owner})
	case "all":
		responder.RespondWithData(ctx, n.Acl)
	}

	return
}

func parseAclRequestTyped(ctx context.Context) (ids []string, err error) {
	var users []string
	query := ctx.HttpRequest().URL.Query()
	params, _, err := request.ParseMultipartForm(ctx.HttpRequest())
	if _, ok := query["users"]; ok && err != nil && err.Error() == "request Content-Type isn't multipart/form-data" {
		users = strings.Split(query.Get("users"), ",")
	} else if params["users"] != "" {
		users = strings.Split(params["users"], ",")
	} else {
		return nil, errors.New("Action requires list of comma separated usernames in 'users' parameter")
	}
	for _, v := range users {
		if uuid.Parse(v) != nil {
			ids = append(ids, v)
		} else {
			u := user.User{Username: v}
			if err := u.SetMongoInfo(); err != nil {
				return nil, err
			}
			ids = append(ids, u.Uuid)
		}
	}
	return ids, nil
}