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 }
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 }
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 }
// 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) }
// 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) }
// 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 }