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 MiddlewareDatabase(config *periwinkle.Cfg) he.Middleware { return he.Middleware{ Outside: func(req he.Request, handle func(he.Request) he.Response) he.Response { var res he.Response conflict := config.DB.Do(func(transaction *periwinkle.Tx) { req.Things["db"] = transaction res = handle(req) }) if conflict != nil { res = rfc7231.StatusConflict(he.NetStringer{conflict}) } return res }, } }
func (usr *userSubscriptions) Methods() map[string]func(he.Request) he.Response { return map[string]func(he.Request) he.Response{ "GET": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) usr.groupID = req.URL.Query().Get("group_id") usr.values = usr.backend().GetFrontEndSubscriptions(db) return rfc7231.StatusOK(usr) }, "POST": func(req he.Request) he.Response { db := req.Things["db"].(*periwinkle.Tx) sess := req.Things["session"].(*backend.Session) type postfmt struct { GroupID string `json:"group_id"` Medium string `json:"medium,omitempty"` Address string `json:"address,omitempty"` } var entity postfmt httperr := safeDecodeJSON(req.Entity, &entity) if httperr != nil { return *httperr } entity.GroupID = strings.ToLower(entity.GroupID) var address *backend.UserAddress if entity.Medium == "" && entity.Address == "" { address = &usr.Addresses[0] for _, addr := range usr.Addresses { if addr.SortOrder < address.SortOrder { address = &addr } } } else { for _, addr := range usr.Addresses { if addr.Medium == entity.Medium && addr.Address == entity.Address { address = &addr break } } } if address == nil { return rfc7231.StatusConflict(he.NetPrintf("You don't have that address")) } backend.NewSubscription(db, address.ID, entity.GroupID, sess != nil && sess.UserID == usr.ID) return rfc7231.StatusCreated(usr, entity.GroupID+":"+address.Medium+":"+address.Address, req) }, } }
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 }
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() }, } }