Exemplo n.º 1
0
// User requests or updates a self-subscription to a topic
//	h 		- hub
//	sess 	- originating session
//  pktId 	- originating packet Id
//	want	- requested access mode
//	info 	- explanation info given by the requester
//	private	- private value to assign to the subscription
// Handle these cases:
// A. User is trying to subscribe for the first time (no subscription)
// B. User is already subscribed, just joining without changing anything
// C. User is responsing to an earlier invite (modeWant was "N" in subscription)
// D. User is already subscribed, changing modeWant
// E. User is accepting ownership transfer (requesting ownership transfer is not permitted)
func (t *Topic) requestSub(h *Hub, sess *Session, pktId string, want string, info,
	private interface{}, loaded bool) error {

	log.Println("requestSub", t.name)

	now := time.Now().UTC().Round(time.Millisecond)

	// Parse the acess mode requested by the user
	var modeWant types.AccessMode
	var explicitWant bool
	if want != "" {
		log.Println("mode want explicit: ", want)
		modeWant.UnmarshalText([]byte(want))
		explicitWant = true
	}

	// If the user wants a self-ban, make sure it's the only change
	if modeWant.IsBanned() {
		modeWant = types.ModeBanned
	}

	// Vars for saving changes to access mode
	var updWant *types.AccessMode
	var updGiven *types.AccessMode

	// Check if it's an attempt at a new subscription to the topic. If so, save it to database
	userData, existingSub := t.perUser[sess.uid]
	if !existingSub {
		// User requested default access mode.
		// modeWant could still be ModeNone if the owner wants to manually approve every request
		if modeWant == types.ModeNone {
			if explicitWant {
				// The operation is invalid - user requested to clear access to topic which makes no sense.
				sess.queueOut(ErrMalformed(pktId, t.original, now))
				return errors.New("attempt to clear topic access")
			}

			modeWant = t.accessAuth
		}

		userData = perUserData{
			private:   private,
			modeGiven: t.accessAuth,
			modeWant:  modeWant,
		}

		// If it's a re-subscription to a p2p topic, set public
		if t.cat == types.TopicCat_P2P {
			// t.perUser contains just one element - other user
			for uid2, _ := range t.perUser {
				if user2, err := store.Users.Get(uid2); err != nil {
					log.Println(err.Error())
					sess.queueOut(ErrUnknown(pktId, t.original, now))
					return err
				} else {
					userData.public = user2.Public
				}
				break
			}
		}

		// Add subscription to database
		sub := &types.Subscription{
			User:      sess.uid.String(),
			Topic:     t.name,
			ModeWant:  userData.modeWant,
			ModeGiven: userData.modeGiven,
			Private:   userData.private,
		}

		if err := store.Subs.Create(sub); err != nil {
			log.Println(err.Error())
			sess.queueOut(ErrUnknown(pktId, t.original, now))
			return err
		}

	} else {
		var ownerChange bool
		// Process update to existing subscription. It could be an incomplete subscription for a new topic.

		// If user did not request a new access mode, copy one from cache
		if !explicitWant && modeWant == types.ModeNone {
			modeWant = userData.modeWant
		}

		if userData.modeGiven.IsOwner() {
			// Check for possible ownership transfer. Handle the following cases:
			// 1. Owner joining the topic without any changes
			// 2. Owner changing own settings
			// 3. Acceptance or rejection of the ownership transfer

			// Make sure the current owner cannot unset the owner flag or ban himself
			if t.owner == sess.uid && (!modeWant.IsOwner() || modeWant.IsBanned()) {
				log.Println("requestSub: owner attempts to unset the owner flag")
				sess.queueOut(ErrMalformed(pktId, t.original, now))
				return errors.New("cannot unset ownership or ban the owner")
			}

			// Ownership transfer
			ownerChange = modeWant.IsOwner() && !userData.modeWant.IsOwner()

			// The owner should be able to grant himself any access permissions
			// If ownership transfer is rejected don't upgrade
			if modeWant.IsOwner() && !userData.modeGiven.Check(modeWant) {
				userData.modeGiven |= modeWant
				updGiven = &userData.modeGiven
			}
		} else if modeWant.IsOwner() {
			// Ownership transfer can only be initiated by the owner
			sess.queueOut(ErrMalformed(pktId, t.original, now))
			return errors.New("non-owner cannot request ownership transfer")
		} else if userData.modeGiven.IsManager() && modeWant.IsManager() {
			// The sharer should be able to grant any permissions except ownership
			if !userData.modeGiven.Check(modeWant & ^types.ModeBanned) {
				userData.modeGiven |= (modeWant & ^types.ModeBanned)
				updGiven = &userData.modeGiven
			}
		}

		// Check if t.accessAuth is different now than at the last attempt to subscribe
		if userData.modeGiven == types.ModeNone && t.accessAuth != types.ModeNone {
			userData.modeGiven = t.accessAuth
			updGiven = &userData.modeGiven
		}

		// This is a request to change access to whatever is currently available
		if modeWant == types.ModeNone {
			modeWant = userData.modeGiven
		}

		// Access actually changed
		if userData.modeWant != modeWant {
			userData.modeWant = modeWant
			updWant = &modeWant
		}

		// Save changes to DB
		if updWant != nil || updGiven != nil {
			update := map[string]interface{}{}
			// FIXME(gene): gorethink has a bug which causes ModeXYZ to be saved as a string, converting to int
			if updWant != nil {
				update["ModeWant"] = int(*updWant)
			}
			if updGiven != nil {
				update["ModeGiven"] = int(*updGiven)
			}
			if err := store.Subs.Update(t.name, sess.uid, update); err != nil {
				sess.queueOut(ErrUnknown(pktId, t.original, now))
				return err
			}
			//log.Printf("requestSub: topic %s updated SUB: %+#v", t.name, update)
		}

		// No transactions in RethinkDB, but two owners are better than none
		if ownerChange {
			//log.Printf("requestSub: topic %s owner change", t.name)

			userData = t.perUser[t.owner]
			userData.modeGiven = (userData.modeGiven & ^types.ModeOwner)
			userData.modeWant = (userData.modeWant & ^types.ModeOwner)
			if err := store.Subs.Update(t.name, t.owner,
				// FIXME(gene): gorethink has a bug which causes ModeXYZ to be saved as a string, converting to int
				map[string]interface{}{
					"ModeWant":  int(userData.modeWant),
					"ModeGiven": int(userData.modeGiven)}); err != nil {
				return err
			}
			t.perUser[t.owner] = userData
			t.owner = sess.uid
		}
	}

	t.perUser[sess.uid] = userData

	// If the user is (self)banned from topic, no further action is needed
	if modeWant.IsBanned() {
		t.evictUser(sess.uid, false, sess)
		// FIXME(gene): need to send a reply to user
		return errors.New("self-banned access to topic")
	} else if userData.modeGiven.IsBanned() {
		sess.queueOut(ErrPermissionDenied(pktId, t.original, now))
		return errors.New("topic access denied")
	}

	// Don't report existing subscription (no value);
	// Don't report a newly loaded Grp topic because it's announced later
	if !existingSub && (t.cat == types.TopicCat_P2P || !loaded) {
		t.presTopicSubscribed(sess.uid, sess)
	} else if existingSub {
		log.Println("pres not published: existing sub")
	} else {
		log.Println("pres not published: topic just loaded")
	}

	// If requested access mode different from given:
	if !userData.modeGiven.Check(modeWant) {
		// Send req to approve to topic managers
		for uid, pud := range t.perUser {
			if pud.modeGiven&pud.modeWant&types.ModeShare != 0 {
				h.route <- t.makeInvite(uid, sess.uid, sess.uid, types.InvAppr,
					modeWant, userData.modeGiven, info)
			}
		}
		// Send info to requester
		h.route <- t.makeInvite(sess.uid, sess.uid, sess.uid, types.InvInfo, modeWant, userData.modeGiven, t.public)
	}

	return nil
}
Exemplo n.º 2
0
// approveSub processes a request to initiate an invite or approve a subscription request from another user:
// Handle these cases:
// A. Manager is inviting another user for the first time (no prior subscription)
// B. Manager is re-inviting another user (adjusting modeGiven, modeWant is still "N")
// C. Manager is changing modeGiven for another user, modeWant != "N"
func (t *Topic) approveSub(h *Hub, sess *Session, target types.Uid, set *MsgClientSet) error {
	now := time.Now().UTC().Round(time.Millisecond)

	// Check if requester actually has permission to manage sharing
	if userData, ok := t.perUser[sess.uid]; !ok || !userData.modeGiven.IsManager() || !userData.modeWant.IsManager() {
		sess.queueOut(ErrPermissionDenied(set.Id, t.original, now))
		return errors.New("topic access denied")
	}

	// Parse the access mode granted
	var modeGiven types.AccessMode
	if set.Sub.Mode != "" {
		modeGiven.UnmarshalText([]byte(set.Sub.Mode))
	}

	// If the user is banned from topic, make sute it's the only change
	if modeGiven.IsBanned() {
		modeGiven = types.ModeBanned
	}

	// Make sure no one but the owner can do an ownership transfer
	if modeGiven.IsOwner() && t.owner != sess.uid {
		sess.queueOut(ErrPermissionDenied(set.Id, t.original, now))
		return errors.New("attempt to transfer ownership by non-owner")
	}

	var givenBefore types.AccessMode
	// Check if it's a new invite. If so, save it to database as a subscription.
	// Saved subscription does not mean the user is allowed to post/read
	userData, ok := t.perUser[target]
	if !ok {
		if modeGiven == types.ModeNone {
			if t.accessAuth != types.ModeNone {
				// Request to use default access mode for the new subscriptions.
				modeGiven = t.accessAuth
			} else {
				sess.queueOut(ErrMalformed(set.Id, t.original, now))
				return errors.New("cannot invite without giving any access rights")
			}
		}

		// Add subscription to database
		sub := &types.Subscription{
			User:      target.String(),
			Topic:     t.name,
			ModeWant:  types.ModeNone,
			ModeGiven: modeGiven,
		}

		if err := store.Subs.Create(sub); err != nil {
			sess.queueOut(ErrUnknown(set.Id, t.original, now))
			return err
		}

		userData = perUserData{
			modeGiven: sub.ModeGiven,
			modeWant:  sub.ModeWant,
			private:   nil,
		}

		t.perUser[target] = userData

	} else {
		// Action on an existing subscription (re-invite or confirm/decline)
		givenBefore = userData.modeGiven

		// Request to re-send invite without changing the access mode
		if modeGiven == types.ModeNone {
			modeGiven = userData.modeGiven
		} else if modeGiven != userData.modeGiven {
			userData.modeGiven = modeGiven

			// Save changed value to database
			if err := store.Subs.Update(t.name, sess.uid,
				map[string]interface{}{"ModeGiven": modeGiven}); err != nil {
				return err
			}

			t.perUser[target] = userData
		}
	}

	// The user does not want to be bothered, no further action is needed
	if userData.modeWant.IsBanned() {
		sess.queueOut(ErrPermissionDenied(set.Id, t.original, now))
		return errors.New("topic access denied")
	}

	// Handle the following cases:
	// * a ban of the user, modeGive.IsBanned = true (if user is banned no need to invite anyone)
	// * regular invite: modeWant = "N", modeGiven > 0
	// * access rights update: old modeGiven != new modeGiven
	if !modeGiven.IsBanned() {
		if userData.modeWant == types.ModeNone {
			// (re-)Send the invite to target
			h.route <- t.makeInvite(target, target, sess.uid, types.InvJoin, userData.modeWant, modeGiven,
				set.Sub.Info)
		} else if givenBefore != modeGiven {
			// Inform target that the access has changed
			h.route <- t.makeInvite(target, target, sess.uid, types.InvInfo, userData.modeWant, modeGiven,
				set.Sub.Info)
		}
	}

	// Has anything actually changed?
	if givenBefore != modeGiven {
		// inform requester of the change made
		h.route <- t.makeInvite(sess.uid, target, sess.uid, types.InvInfo, userData.modeWant, modeGiven,
			map[string]string{"before": givenBefore.String()})
	}

	return nil
}
Exemplo n.º 3
0
func gen_rethink(reset bool, dbsource string, data *Data) {
	var err error

	log.Println("Opening DB...")

	err = store.Open("rethinkdb", dbsource)
	if err != nil {
		log.Fatal("Failed to connect to DB: ", err)
	}
	defer store.Close()

	log.Println("Initializing DB...")

	err = store.InitDb(reset)
	if err != nil {
		if strings.Contains(err.Error(), " already exists") {
			log.Println("DB already exists, NOT reinitializing")
		} else {
			log.Fatal("Failed to init DB: ", err)
		}
	} else {
		log.Println("DB successfully initialized")

	}
	if data.Users == nil {
		log.Println("No data provided, stopping")
		return
	}

	nameIndex := make(map[string]string, len(data.Users))

	log.Println("Generating users...")

	for _, uu := range data.Users {
		if uu["createdAt"] != nil {

		}
		user := types.User{
			State:    int(uu["state"].(float64)),
			Username: uu["username"].(string),
			Access: types.DefaultAccess{
				Auth: types.ModePublic,
				Anon: types.ModeNone,
			},
			Public: uu["public"],
		}
		user.CreatedAt = getCreatedTime(uu["createdAt"])

		// store.Users.Create will subscribe user to !me topic but won't create a !me topic
		if _, err := store.Users.Create(&user, "basic",
			uu["username"].(string)+":"+uu["passhash"].(string), uu["private"]); err != nil {
			log.Fatal(err)
		}

		nameIndex[user.Username] = user.Id

		log.Printf("Created user '%s' as %s (%d)", user.Username, user.Id, user.Uid())
	}

	log.Println("Generating group topics...")

	for _, gt := range data.Grouptopics {
		name := genTopicName()
		nameIndex[gt["name"].(string)] = name

		topic := &types.Topic{Name: name, Public: gt["public"]}
		var owner types.Uid
		if gt["owner"] != nil {
			owner = types.ParseUid(nameIndex[gt["owner"].(string)])
			topic.GiveAccess(owner, types.ModeFull, types.ModeFull)
		}
		topic.CreatedAt = getCreatedTime(gt["createdAt"])

		if err = store.Topics.Create(topic, owner, gt["private"]); err != nil {
			log.Fatal(err)
		}
		log.Printf("Created topic '%s' as %s", gt["name"].(string), name)
	}

	log.Println("Generating P2P subscriptions...")

	p2pIndex := map[string][]map[string]interface{}{}

	for _, ss := range data.Subscriptions {
		u1 := ss["user"].(string)
		u2 := ss["topic"].(string)

		if u2[0] == '*' {
			// skip group topics
			continue
		}

		var pair string
		var idx int
		if u1 < u2 {
			pair = u1 + ":" + u2
			idx = 0
		} else {
			pair = u2 + ":" + u1
			idx = 1
		}
		if _, ok := p2pIndex[pair]; !ok {
			p2pIndex[pair] = make([]map[string]interface{}, 2)
		}

		p2pIndex[pair][idx] = ss
	}

	log.Printf("Collected p2p pairs: %d\n", len(p2pIndex))

	for pair, subs := range p2pIndex {
		uid1 := types.ParseUid(nameIndex[subs[0]["user"].(string)])
		uid2 := types.ParseUid(nameIndex[subs[1]["user"].(string)])
		topic := uid1.P2PName(uid2)
		created0 := getCreatedTime(subs[0]["createdAt"])
		created1 := getCreatedTime(subs[1]["createdAt"])
		var s0want, s0given, s1want, s1given types.AccessMode
		if err := s0want.UnmarshalText([]byte(subs[0]["modeWant"].(string))); err != nil {
			log.Fatal(err)
		}
		if err := s0given.UnmarshalText([]byte(subs[0]["modeHave"].(string))); err != nil {
			log.Fatal(err)
		}

		if err := s1want.UnmarshalText([]byte(subs[1]["modeWant"].(string))); err != nil {
			log.Fatal(err)
		}
		if err := s1given.UnmarshalText([]byte(subs[1]["modeHave"].(string))); err != nil {
			log.Fatal(err)
		}

		log.Printf("Processing %s (%s), %s, %s", pair, topic, uid1.String(), uid2.String())
		err := store.Topics.CreateP2P(
			&types.Subscription{
				ObjHeader: types.ObjHeader{CreatedAt: created0},
				User:      uid1.String(),
				Topic:     topic,
				ModeWant:  s0want,
				ModeGiven: s0given,
				Private:   subs[0]["private"]},
			&types.Subscription{
				ObjHeader: types.ObjHeader{CreatedAt: created1},
				User:      uid2.String(),
				Topic:     topic,
				ModeWant:  s1want,
				ModeGiven: s1given,
				Private:   subs[1]["private"]})

		if err != nil {
			log.Fatal(err)
		}
	}

	log.Println("Generating group subscriptions...")

	for _, ss := range data.Subscriptions {

		u1 := nameIndex[ss["user"].(string)]
		u2 := nameIndex[ss["topic"].(string)]

		var want, given types.AccessMode
		if err := want.UnmarshalText([]byte(ss["modeWant"].(string))); err != nil {
			log.Fatal(err)
		}
		if err := given.UnmarshalText([]byte(ss["modeHave"].(string))); err != nil {
			log.Fatal(err)
		}

		// Define topic name
		name := u2
		if !types.ParseUid(u2).IsZero() {
			// skip p2p subscriptions
			continue
		}

		log.Printf("Sharing '%s' with '%s'", ss["topic"].(string), ss["user"].(string))

		if err = store.Subs.Create(&types.Subscription{
			ObjHeader: types.ObjHeader{CreatedAt: getCreatedTime(ss["createdAt"])},
			User:      u1,
			Topic:     name,
			ModeWant:  want,
			ModeGiven: given,
			Private:   ss["private"]}); err != nil {

			log.Fatal(err)
		}
	}

	log.Println("Generating messages...")

	rand.Seed(time.Now().UnixNano())
	seqIds := map[string]int{}
	var oldFrom types.Uid
	var oldTopic string
	toInsert := 80
	// Starting 4 days ago
	timestamp := time.Now().UTC().Round(time.Millisecond).Add(time.Second * time.Duration(-3600*24*4))
	for i := 0; i < toInsert; i++ {

		sub := data.Subscriptions[rand.Intn(len(data.Subscriptions))]
		topic := nameIndex[sub["topic"].(string)]
		from := types.ParseUid(nameIndex[sub["user"].(string)])
		if topic == oldTopic && from == oldFrom {
			toInsert++
			continue
		}
		oldTopic, oldFrom = topic, from

		if uid := types.ParseUid(topic); !uid.IsZero() {
			topic = uid.P2PName(from)
		}

		seqIds[topic]++
		seqId := seqIds[topic]
		str := data.Messages[rand.Intn(len(data.Messages))]
		// Max time between messages is 2 hours, averate - 1 hour, time is increasing as seqId increases
		timestamp = timestamp.Add(time.Second * time.Duration(rand.Intn(3600*2)))
		msg := types.Message{
			ObjHeader: types.ObjHeader{CreatedAt: timestamp},
			SeqId:     seqId,
			Topic:     topic,
			From:      from.String(),
			Content:   str}
		if err = store.Messages.Save(&msg); err != nil {
			log.Fatal(err)
		}
		log.Printf("Message %d at %v to '%s' '%s'", msg.SeqId, msg.CreatedAt, topic, str)
	}
}