func safeDecodeJSON(in interface{}, out interface{}) *he.Response { decoder, ok := in.(*json.Decoder) if !ok { ret := rfc7231.StatusUnsupportedMediaType(he.NetPrintf("PUT and POST requests must have a document media type")) return &ret } var tmp interface{} err := decoder.Decode(&tmp) if err != nil { ret := rfc7231.StatusUnsupportedMediaType(he.NetPrintf("Couldn't parse: %v", err)) return &ret } str, err := json.Marshal(tmp) if err != nil { panic(err) } err = json.Unmarshal(str, out) if err != nil { ret := rfc7231.StatusUnsupportedMediaType(he.NetPrintf("Request body didn't have expected structure (field had wrong data type): %v", err)) return &ret } if !jsondiff.Equal(tmp, out) { diff, err := jsondiff.NewJSONPatch(tmp, out) if err != nil { panic(err) } entity := decodeJSONError{ message: locale.Sprintf("Request body didn't have expected structure (extra or missing fields). The included diff would make the request acceptable."), diff: diff, } ret := rfc7231.StatusUnsupportedMediaType(entity) return &ret } return nil }
func (o *user) patchPassword(patch *jsonpatch.Patch) *he.Response { // this is in the running for the grossest code I've ever // written, but I think it's the best way to do it --lukeshu type patchop struct { Op string `json:"op"` Path string `json:"path"` Value string `json:"value"` } str, err := json.Marshal(patch) if err != nil { panic(err) } var ops []patchop err = json.Unmarshal(str, &ops) if err != nil { return nil } outOps := make([]patchop, 0, len(ops)) checkedpass := false for _, op := range ops { if op.Path == "/password" { switch op.Op { case "test": if !o.backend().CheckPassword(op.Value) { ret := rfc7231.StatusConflict(he.NetPrintf("old password didn't match")) return &ret } checkedpass = true case "replace": if !checkedpass { ret := rfc7231.StatusUnsupportedMediaType(he.NetPrintf("you must submit and old password (using 'test') before setting a new one")) return &ret } if o.backend().CheckPassword(op.Value) { ret := rfc7231.StatusConflict(he.NetPrintf("that new password is the same as the old one")) return &ret } o.backend().SetPassword(op.Value) default: ret := rfc7231.StatusUnsupportedMediaType(he.NetPrintf("you may only 'set' or 'replace' the password")) return &ret } } else { outOps = append(outOps, op) } } str, err = json.Marshal(outOps) if err != nil { panic(err) } var out jsonpatch.JSONPatch err = json.Unmarshal(str, &out) if err != nil { panic(out) } *patch = out return nil }
func newDirUsers() dirUsers { r := dirUsers{} r.methods = map[string]func(he.Request) he.Response{ "POST": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) type postfmt struct { Username string `json:"username"` Email string `json:"email"` Password string `json:"password"` PasswordVerification string `json:"password_verification,omitempty"` } var entity postfmt httperr := safeDecodeJSON(req.Entity, &entity) if httperr != nil { return *httperr } if entity.Username == "" || entity.Email == "" || entity.Password == "" { return rfc7231.StatusUnsupportedMediaType(he.NetPrintf("username, email, and password can't be emtpy")) } if entity.PasswordVerification != "" { if entity.Password != entity.PasswordVerification { // Passwords don't match return rfc7231.StatusConflict(he.NetPrintf("password and password_verification don't match")) } } usr := backend.NewUser(db, entity.Username, entity.Password, entity.Email) backend.NewUserAddress(db, usr.ID, "noop", backend.RandomString(20), true) backend.NewUserAddress(db, usr.ID, "admin", backend.RandomString(20), true) req.Things["user"] = usr return rfc7231.StatusCreated(r, usr.ID, req) }, } return r }
func (o *group) Methods() map[string]func(he.Request) he.Response { return map[string]func(he.Request) he.Response{ "GET": func(req he.Request) he.Response { var enum Enumerategroup enum = EnumerateGroup(o) return rfc7231.StatusOK(he.NetJSON{Data: enum}) }, "PUT": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) var newGroup group httperr := safeDecodeJSON(req.Entity, &newGroup) if httperr != nil { return *httperr } if o.ID != newGroup.ID { return rfc7231.StatusConflict(he.NetPrintf("Cannot change group id")) } *o = newGroup o.backend().Save(db) return rfc7231.StatusOK(o) }, "PATCH": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) sess := req.Things["session"].(*backend.Session) subscribed := backend.IsSubscribed(db, sess.UserID, *o.backend()) if !backend.IsAdmin(db, sess.UserID, *o.backend()) { if o.JoinPublic == 1 { if subscribed == 0 { return rfc7231.StatusForbidden(he.NetPrintf("Unauthorized user")) } if o.JoinConfirmed == 1 && subscribed == 1 { return rfc7231.StatusForbidden(he.NetPrintf("Unauthorized user")) } if o.JoinMember == 1 { return rfc7231.StatusForbidden(he.NetPrintf("Unauthorized user")) } } } enum := EnumerateGroup(o) var newGroup Enumerategroup patch, ok := req.Entity.(jsonpatch.Patch) if !ok { return rfc7231.StatusUnsupportedMediaType(he.NetPrintf("PATCH request must have a patch media type")) } err := patch.Apply(enum, &newGroup) if err != nil { return rfc7231.StatusConflict(he.NetPrintf("%v", err)) } if o.ID != newGroup.Groupname { return rfc7231.StatusConflict(he.NetPrintf("Cannot change group id")) } *o = RenumerateGroup(newGroup) o.backend().Save(db) return rfc7231.StatusOK(o) }, "DELETE": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) sess := req.Things["session"].(*backend.Session) if !backend.IsAdmin(db, sess.UserID, *o.backend()) { return rfc7231.StatusForbidden(he.NetPrintf("Unauthorized user")) } o.backend().Delete(db) return rfc7231.StatusNoContent() }, } }
func newDirGroups() dirGroups { r := dirGroups{} r.methods = map[string]func(he.Request) he.Response{ "GET": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) sess := req.Things["session"].(*backend.Session) var groups []backend.Group type getfmt struct { visibility string } var entity getfmt httperr := safeDecodeJSON(req.Entity, &entity) if httperr != nil { entity.visibility = "subscribed" } if sess == nil { groups = []backend.Group{} } else if entity.visibility == "subscribed" { groups = backend.GetGroupsByMember(db, *backend.GetUserByID(db, sess.UserID)) } else { //groups = GetAllGroups(db) groups = backend.GetPublicAndSubscribedGroups(db, *backend.GetUserByID(db, sess.UserID)) } type EnumerateGroup struct { Groupname string `json:"groupname"` Post map[string]string `json:"post"` Join map[string]string `json:"join"` Read map[string]string `json:"read"` Existence map[string]string `json:"existence"` Subscriptions []backend.Subscription `json:"subscriptions"` } data := make([]EnumerateGroup, len(groups)) for i, grp := range groups { var enum EnumerateGroup enum.Groupname = grp.ID exist := [...]int{grp.ExistencePublic, grp.ExistenceConfirmed} enum.Existence = backend.ReadExist(exist) read := [...]int{grp.ReadPublic, grp.ReadConfirmed} enum.Read = backend.ReadExist(read) post := [...]int{grp.PostPublic, grp.PostConfirmed, grp.PostMember} enum.Post = backend.PostJoin(post) join := [...]int{grp.JoinPublic, grp.JoinConfirmed, grp.JoinMember} enum.Join = backend.PostJoin(join) enum.Subscriptions = grp.Subscriptions data[i] = enum } return rfc7231.StatusOK(he.NetJSON{Data: data}) }, "POST": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) type Response1 struct { Groupname string `json:"groupname"` Post map[string]string `json:"post"` Join map[string]string `json:"join"` Read map[string]string `json:"read"` Existence map[string]string `json:"existence"` } var entity Response1 httperr := safeDecodeJSON(req.Entity, &entity) if httperr != nil { return *httperr } if entity.Groupname == "" { return rfc7231.StatusUnsupportedMediaType(he.NetPrintf("groupname can't be emtpy")) } grp := backend.NewGroup( db, entity.Groupname, backend.Reverse(entity.Existence), backend.Reverse(entity.Read), backend.Reverse(entity.Post), backend.Reverse(entity.Join), ) sess := req.Things["session"].(*backend.Session) address := backend.GetAddressesByUserAndMedium(db, sess.UserID, "noop")[0] backend.NewSubscription(db, address.ID, grp.ID, true) if grp == nil { return rfc7231.StatusConflict(he.NetPrintf("a group with that name already exists")) } else { return rfc7231.StatusCreated(r, grp.ID, req) } }, } return r }
) var MiddlewarePostHack = he.Middleware{ Outside: func(req he.Request, handle func(he.Request) he.Response) he.Response { if req.Method != "POST" { return handle(req) } decoder, ok := req.Entity.(*json.Decoder) if !ok { return handle(req) } var entity interface{} err := decoder.Decode(&entity) if err != nil { return rfc7231.StatusUnsupportedMediaType(he.ErrorToNetEntity(415, locale.Errorf("Couldn't parse: %v", err))) } hash, ok := entity.(map[string]interface{}) if !ok { return handle(req) } method, ok := hash["_method"].(string) delete(hash, "_method") if ok { req.Method = method } xsrf_token, ok := hash["_xsrf_token"].(string) delete(hash, "_xsrf_token")
func (usr *user) Methods() map[string]func(he.Request) he.Response { return map[string]func(he.Request) he.Response{ "GET": func(req he.Request) he.Response { var addresses []backend.UserAddress for _, addr := range usr.Addresses { if addr.Medium != "noop" && addr.Medium != "admin" { addresses = append(addresses, addr) } } usr.Addresses = addresses return rfc7231.StatusOK(usr) }, "PUT": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) sess := req.Things["session"].(*backend.Session) if sess.UserID != usr.ID { return rfc7231.StatusForbidden(he.NetPrintf("Unauthorized user")) } var newUser user httperr := safeDecodeJSON(req.Entity, &newUser) if httperr != nil { return *httperr } if usr.ID != newUser.ID { return rfc7231.StatusConflict(he.NetPrintf("Cannot change user id")) } // TODO: this won't play nice with the // password hash (because it's private), or // with addresses (because the (private) IDs // need to be made to match up) *usr = newUser usr.backend().Save(db) return rfc7231.StatusOK(usr) }, "PATCH": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) sess := req.Things["session"].(*backend.Session) if sess.UserID != usr.ID { return rfc7231.StatusForbidden(he.NetPrintf("Unauthorized user")) } patch, ok := req.Entity.(jsonpatch.Patch) if !ok { return rfc7231.StatusUnsupportedMediaType(he.NetPrintf("PATCH request must have a patch media type")) } httperr := usr.patchPassword(&patch) if httperr != nil { return *httperr } var newUser user err := patch.Apply(usr, &newUser) if err != nil { return rfc7231.StatusConflict(he.ErrorToNetEntity(409, err)) } if usr.ID != newUser.ID { return rfc7231.StatusConflict(he.NetPrintf("Cannot change user id")) } if newUser.PwHash == nil || len(newUser.PwHash) == 0 { newUser.PwHash = usr.PwHash } *usr = newUser usr.backend().Save(db) return rfc7231.StatusOK(usr) }, "DELETE": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) usr.backend().Delete(db) return rfc7231.StatusNoContent() }, } }