// 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 }
// 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 }
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) } }