Beispiel #1
1
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
}
Beispiel #2
0
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
}
Beispiel #3
0
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
}
Beispiel #4
0
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()
		},
	}
}
Beispiel #5
0
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")
Beispiel #7
0
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()
		},
	}
}