コード例 #1
0
ファイル: topic.go プロジェクト: ycaihua/chat
// 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
}
コード例 #2
0
ファイル: topic.go プロジェクト: ycaihua/chat
// 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
}