func (rb *RoomBinding) RenameUser(ctx scope.Context, session proto.Session, formerName string) ( *proto.NickEvent, error) { presence := &Presence{ Room: rb.Name, ServerID: rb.desc.ID, ServerEra: rb.desc.Era, SessionID: session.ID(), Updated: time.Now(), } err := presence.SetFact(&proto.Presence{ SessionView: *session.View(), LastInteracted: presence.Updated, }) if err != nil { return nil, fmt.Errorf("presence marshal error: %s", err) } if _, err := rb.DbMap.Update(presence); err != nil { return nil, fmt.Errorf("presence update error: %s", err) } event := &proto.NickEvent{ SessionID: session.ID(), ID: session.Identity().ID(), From: formerName, To: session.Identity().Name(), } return event, rb.Backend.broadcast(ctx, rb.Room, proto.NickEventType, event, session) }
func (rb *RoomBinding) Snapshot( ctx scope.Context, session proto.Session, level proto.PrivilegeLevel, numMessages int) (*proto.SnapshotEvent, error) { snapshot := &proto.SnapshotEvent{ Identity: session.Identity().ID(), Nick: session.Identity().Name(), SessionID: session.ID(), Version: rb.Version(), } // TODO: do all this in a transaction listing, err := rb.Listing(ctx, level, session) if err != nil { return nil, err } snapshot.Listing = listing log, err := rb.Latest(ctx, numMessages, 0) if err != nil { return nil, err } snapshot.Log = log return snapshot, nil }
func (r *RoomBase) Snapshot( ctx scope.Context, session proto.Session, level proto.PrivilegeLevel, numMessages int) (*proto.SnapshotEvent, error) { snapshot := &proto.SnapshotEvent{ Identity: session.Identity().ID(), Nick: session.Identity().Name(), SessionID: session.ID(), Version: r.Version(), } listing, err := r.Listing(ctx, level, session) if err != nil { return nil, err } snapshot.Listing = listing log, err := r.Latest(ctx, numMessages, 0) if err != nil { return nil, err } snapshot.Log = log if level == proto.General { for i := range snapshot.Log { snapshot.Log[i].Sender.ClientAddress = "" } } return snapshot, nil }
func isExcluded(toCheck proto.Session, excluding []proto.Session) bool { for _, excSess := range excluding { if toCheck.ID() == excSess.ID() { return true } } return false }
func (r *memRoom) RenameUser( ctx scope.Context, session proto.Session, formerName string) (*proto.NickEvent, error) { backend.Logger(ctx).Printf( "renaming %s from %s to %s\n", session.ID(), formerName, session.Identity().Name()) payload := &proto.NickEvent{ SessionID: session.ID(), ID: session.Identity().ID(), From: formerName, To: session.Identity().Name(), } return payload, r.broadcast(ctx, proto.NickType, payload, session) }
func (b *Backend) part(ctx scope.Context, rb *RoomBinding, session proto.Session) error { t, err := b.DbMap.Begin() if err != nil { return err } _, err = t.Exec( "DELETE FROM presence WHERE room = $1 AND server_id = $2 AND server_era = $3 AND session_id = $4", rb.RoomName, b.desc.ID, b.desc.Era, session.ID()) if err != nil { rollback(ctx, t) logging.Logger(ctx).Printf("failed to persist departure: %s", err) return err } // Broadcast a presence event. // TODO: make this an explicit action via the Room protocol, to support encryption event := proto.PresenceEvent(session.View(proto.Staff)) if err := rb.broadcast(ctx, t, proto.PartEventType, event, session); err != nil { rollback(ctx, t) return err } if err := t.Commit(); err != nil { return err } b.Lock() if lm, ok := b.listeners[rb.RoomName]; ok { delete(lm, session.ID()) } b.Unlock() return nil }
func (r *RoomBase) Join(ctx scope.Context, session proto.Session) (string, error) { client := &proto.Client{} if !client.FromContext(ctx) { return "", fmt.Errorf("client data not found in scope") } r.m.Lock() defer r.m.Unlock() if r.identities == nil { r.identities = map[proto.UserID]proto.Identity{} } if r.nicks == nil { r.nicks = map[proto.UserID]string{} } if r.live == nil { r.live = map[proto.UserID][]proto.Session{} } if r.clients == nil { r.clients = map[string]*proto.Client{} } ident := session.Identity() id := ident.ID() if banned, ok := r.agentBans[ident.ID()]; ok && banned.After(time.Now()) { return "", proto.ErrAccessDenied } if banned, ok := r.ipBans[client.IP]; ok && banned.After(time.Now()) { return "", proto.ErrAccessDenied } if _, ok := r.identities[id]; !ok { r.identities[id] = ident } r.live[id] = append(r.live[id], session) r.clients[session.ID()] = client event := proto.PresenceEvent(session.View(proto.Staff)) return "virt:" + event.RealClientAddress, r.broadcast(ctx, proto.JoinType, &event, session) }
func (r *RoomBase) Part(ctx scope.Context, session proto.Session) error { r.m.Lock() defer r.m.Unlock() ident := session.Identity() id := ident.ID() live := r.live[id] for i, s := range live { if s == session { copy(live[i:], live[i+1:]) r.live[id] = live[:len(live)-1] } } if len(r.live[id]) == 0 { delete(r.live, id) delete(r.identities, id) } delete(r.clients, session.ID()) event := proto.PresenceEvent(session.View(proto.Staff)) return r.broadcast(ctx, proto.PartEventType, &event, session) }
func (b *Backend) part(ctx scope.Context, room *Room, session proto.Session) error { b.Lock() defer b.Unlock() if lm, ok := b.listeners[room.Name]; ok { delete(lm, session.ID()) } _, err := b.DbMap.Exec( "DELETE FROM presence"+ " WHERE room = $1 AND server_id = $2 AND server_era = $3 AND session_id = $4", room.Name, b.desc.ID, b.desc.Era, session.ID()) if err != nil { backend.Logger(ctx).Printf("failed to persist departure: %s", err) } // Broadcast a presence event. // TODO: make this an explicit action via the Room protocol, to support encryption return b.broadcast(ctx, room, proto.PartEventType, proto.PresenceEvent(*session.View()), session) }
func (r *memRoom) Join(ctx scope.Context, session proto.Session) error { client := &proto.Client{} if !client.FromContext(ctx) { return fmt.Errorf("client data not found in scope") } r.m.Lock() defer r.m.Unlock() if r.identities == nil { r.identities = map[proto.UserID]proto.Identity{} } if r.live == nil { r.live = map[proto.UserID][]proto.Session{} } if r.clients == nil { r.clients = map[string]*proto.Client{} } ident := session.Identity() id := ident.ID() if banned, ok := r.agentBans[ident.ID()]; ok && banned.After(time.Now()) { return proto.ErrAccessDenied } if banned, ok := r.ipBans[client.IP]; ok && banned.After(time.Now()) { return proto.ErrAccessDenied } if _, ok := r.identities[id]; !ok { r.identities[id] = ident } r.live[id] = append(r.live[id], session) r.clients[session.ID()] = client return r.broadcast(ctx, proto.JoinType, proto.PresenceEvent(*session.View()), session) }
func (r *RoomBase) RenameUser( ctx scope.Context, session proto.Session, formerName string) (*proto.NickEvent, error) { r.m.Lock() defer r.m.Unlock() logging.Logger(ctx).Printf( "renaming %s from %s to %s\n", session.ID(), formerName, session.Identity().Name()) r.nicks[session.Identity().ID()] = session.Identity().Name() payload := &proto.NickEvent{ SessionID: session.ID(), ID: session.Identity().ID(), From: formerName, To: session.Identity().Name(), } return payload, r.broadcast(ctx, proto.NickType, payload, session) }
func (rb *RoomBinding) RenameUser(ctx scope.Context, session proto.Session, formerName string) ( *proto.NickEvent, error) { presence := &Presence{ Room: rb.RoomName, ServerID: rb.desc.ID, ServerEra: rb.desc.Era, SessionID: session.ID(), Updated: time.Now(), } err := presence.SetFact(&proto.Presence{ SessionView: session.View(proto.Staff), LastInteracted: presence.Updated, }) if err != nil { return nil, fmt.Errorf("presence marshal error: %s", err) } t, err := rb.DbMap.Begin() if err != nil { return nil, err } if _, err := t.Update(presence); err != nil { rollback(ctx, t) return nil, fmt.Errorf("presence update error: %s", err) } // Store latest nick. // Loop in read-committed mode to simulate UPSERT. for { // Try to insert; if this fails due to duplicate key value, try to update. nick := &Nick{ Room: rb.RoomName, UserID: string(session.Identity().ID()), Nick: session.Identity().Name(), } if err := rb.DbMap.Insert(nick); err != nil { if !strings.HasPrefix(err.Error(), "pq: duplicate key value") { rollback(ctx, t) return nil, err } } else { break } n, err := rb.DbMap.Update(nick) if err != nil { rollback(ctx, t) return nil, err } if n > 0 { break } } event := &proto.NickEvent{ SessionID: session.ID(), ID: session.Identity().ID(), From: formerName, To: session.Identity().Name(), } if err := rb.broadcast(ctx, t, proto.NickEventType, event, session); err != nil { rollback(ctx, t) return nil, err } if err := t.Commit(); err != nil { return nil, err } return event, nil }
func (rb *RoomBinding) EditMessage( ctx scope.Context, session proto.Session, edit proto.EditMessageCommand) ( proto.EditMessageReply, error) { var reply proto.EditMessageReply editID, err := snowflake.New() if err != nil { return reply, err } cols, err := allColumns(rb.DbMap, Message{}, "") if err != nil { return reply, err } t, err := rb.DbMap.Begin() if err != nil { return reply, err } rollback := func() { if err := t.Rollback(); err != nil { logging.Logger(ctx).Printf("rollback error: %s", err) } } var msg Message err = t.SelectOne(&msg, fmt.Sprintf("SELECT %s FROM message WHERE room = $1 AND id = $2", cols), rb.RoomName, edit.ID.String()) if err != nil { rollback() return reply, err } if msg.PreviousEditID.Valid && msg.PreviousEditID.String != edit.PreviousEditID.String() { rollback() return reply, proto.ErrEditInconsistent } entry := &MessageEditLog{ EditID: editID.String(), Room: rb.RoomName, MessageID: edit.ID.String(), PreviousEditID: msg.PreviousEditID, PreviousContent: msg.Content, PreviousParent: sql.NullString{ String: msg.Parent, Valid: true, }, } // TODO: tests pass in a nil session, until we add support for the edit command if session != nil { entry.EditorID = sql.NullString{ String: string(session.Identity().ID()), Valid: true, } } if err := t.Insert(entry); err != nil { rollback() return reply, err } now := time.Time(proto.Now()) sets := []string{"edited = $3", "previous_edit_id = $4"} args := []interface{}{rb.RoomName, edit.ID.String(), now, editID.String()} msg.Edited = gorp.NullTime{Valid: true, Time: now} if edit.Content != "" { args = append(args, edit.Content) sets = append(sets, fmt.Sprintf("content = $%d", len(args))) msg.Content = edit.Content } if edit.Parent != 0 { args = append(args, edit.Parent.String()) sets = append(sets, fmt.Sprintf("parent = $%d", len(args))) msg.Parent = edit.Parent.String() } if edit.Delete != msg.Deleted.Valid { if edit.Delete { args = append(args, now) sets = append(sets, fmt.Sprintf("deleted = $%d", len(args))) msg.Deleted = gorp.NullTime{Valid: true, Time: now} } else { sets = append(sets, "deleted = NULL") msg.Deleted.Valid = false } } query := fmt.Sprintf("UPDATE message SET %s WHERE room = $1 AND id = $2", strings.Join(sets, ", ")) if _, err := t.Exec(query, args...); err != nil { rollback() return reply, err } if edit.Announce { event := &proto.EditMessageEvent{ EditID: editID, Message: msg.ToTransmission(), } err = rb.broadcast(ctx, t, proto.EditMessageEventType, event, session) if err != nil { rollback() return reply, err } } if err := t.Commit(); err != nil { return reply, err } reply.EditID = editID reply.Message = msg.ToTransmission() return reply, nil }
func (b *Backend) join(ctx scope.Context, rb *RoomBinding, session proto.Session) (string, error) { client := &proto.Client{} if !client.FromContext(ctx) { return "", fmt.Errorf("client data not found in scope") } bannedAgentCols, err := allColumns(b.DbMap, BannedAgent{}, "") if err != nil { return "", err } bannedIPCols, err := allColumns(b.DbMap, BannedIP{}, "") if err != nil { return "", err } t, err := b.DbMap.Begin() if err != nil { return "", err } // Check for agent ID bans. agentBans, err := t.Select( BannedAgent{}, fmt.Sprintf( "SELECT %s FROM banned_agent WHERE agent_id = $1 AND (room IS NULL OR room = $2) AND (expires IS NULL OR expires > NOW())", bannedAgentCols), session.Identity().ID().String(), rb.RoomName) if err != nil { rollback(ctx, t) return "", err } if len(agentBans) > 0 { logging.Logger(ctx).Printf("access denied to %s: %#v", session.Identity().ID(), agentBans) if err := t.Rollback(); err != nil { return "", err } return "", proto.ErrAccessDenied } // Check for IP bans. ipBans, err := t.Select( BannedIP{}, fmt.Sprintf( "SELECT %s FROM banned_ip WHERE ip = $1 AND (room IS NULL OR room = $2) AND (expires IS NULL OR expires > NOW())", bannedIPCols), client.IP, rb.RoomName) if err != nil { rollback(ctx, t) return "", err } if len(ipBans) > 0 { logging.Logger(ctx).Printf("access denied to %s: %#v", client.IP, ipBans) if err := t.Rollback(); err != nil { return "", err } return "", proto.ErrAccessDenied } // Virtualize the session's client address. var row struct { Address string `db:"address"` } if err := t.SelectOne(&row, "SELECT virtualize_address($1, $2::inet) AS address", rb.RoomName, client.IP); err != nil { return "", err } virtualAddress := row.Address // Look up session's nick. nickRow, err := t.Get(Nick{}, string(session.Identity().ID()), rb.RoomName) if err != nil && err != sql.ErrNoRows { return "", err } if nickRow != nil { session.SetName(nickRow.(*Nick).Nick) } // Write to session log. // TODO: do proper upsert simulation entry := &SessionLog{ SessionID: session.ID(), IP: client.IP, Room: rb.RoomName, UserAgent: client.UserAgent, Connected: client.Connected, } if _, err := t.Delete(entry); err != nil { if rerr := t.Rollback(); rerr != nil { logging.Logger(ctx).Printf("rollback error: %s", rerr) } return "", err } if err := t.Insert(entry); err != nil { if rerr := t.Rollback(); rerr != nil { logging.Logger(ctx).Printf("rollback error: %s", rerr) } return "", err } // Broadcast a presence event. // TODO: make this an explicit action via the Room protocol, to support encryption presence := &Presence{ Room: rb.RoomName, ServerID: b.desc.ID, ServerEra: b.desc.Era, SessionID: session.ID(), Updated: time.Now(), } sessionView := session.View(proto.Staff) sessionView.ClientAddress = virtualAddress err = presence.SetFact(&proto.Presence{ SessionView: sessionView, LastInteracted: presence.Updated, }) if err != nil { rollback(ctx, t) return "", fmt.Errorf("presence marshal error: %s", err) } if err := t.Insert(presence); err != nil { return "", fmt.Errorf("presence insert error: %s", err) } b.Lock() // Add session to listeners. lm, ok := b.listeners[rb.RoomName] if !ok { lm = ListenerMap{} b.listeners[rb.RoomName] = lm } lm[session.ID()] = Listener{Session: session, Client: client} b.Unlock() event := proto.PresenceEvent(session.View(proto.Staff)) event.ClientAddress = virtualAddress if err := rb.broadcast(ctx, t, proto.JoinEventType, event, session); err != nil { rollback(ctx, t) return "", err } if err := t.Commit(); err != nil { return "", err } return virtualAddress, nil }
func (b *Backend) join(ctx scope.Context, room *Room, session proto.Session) error { client := &proto.Client{} if !client.FromContext(ctx) { return fmt.Errorf("client data not found in scope") } bannedAgentCols, err := allColumns(b.DbMap, BannedAgent{}, "") if err != nil { return err } bannedIPCols, err := allColumns(b.DbMap, BannedIP{}, "") if err != nil { return err } t, err := b.DbMap.Begin() if err != nil { return err } rb := func() { rollback(ctx, t) } // Check for agent ID bans. agentBans, err := t.Select( BannedAgent{}, fmt.Sprintf( "SELECT %s FROM banned_agent WHERE agent_id = $1 AND (room IS NULL OR room = $2) AND (expires IS NULL OR expires > NOW())", bannedAgentCols), session.Identity().ID().String(), room.Name) if err != nil { rb() return err } if len(agentBans) > 0 { logging.Logger(ctx).Printf("access denied to %s: %#v", session.Identity().ID(), agentBans) if err := t.Rollback(); err != nil { return err } return proto.ErrAccessDenied } // Check for IP bans. ipBans, err := t.Select( BannedIP{}, fmt.Sprintf( "SELECT %s FROM banned_ip WHERE ip = $1 AND (room IS NULL OR room = $2) AND (expires IS NULL OR expires > NOW())", bannedIPCols), client.IP, room.Name) if err != nil { rb() return err } if len(ipBans) > 0 { logging.Logger(ctx).Printf("access denied to %s: %#v", client.IP, ipBans) if err := t.Rollback(); err != nil { return err } return proto.ErrAccessDenied } // Write to session log. // TODO: do proper upsert simulation entry := &SessionLog{ SessionID: session.ID(), IP: client.IP, Room: room.Name, UserAgent: client.UserAgent, Connected: client.Connected, } if _, err := t.Delete(entry); err != nil { if rerr := t.Rollback(); rerr != nil { logging.Logger(ctx).Printf("rollback error: %s", rerr) } return err } if err := t.Insert(entry); err != nil { if rerr := t.Rollback(); rerr != nil { logging.Logger(ctx).Printf("rollback error: %s", rerr) } return err } // Broadcast a presence event. // TODO: make this an explicit action via the Room protocol, to support encryption presence := &Presence{ Room: room.Name, ServerID: b.desc.ID, ServerEra: b.desc.Era, SessionID: session.ID(), Updated: time.Now(), } err = presence.SetFact(&proto.Presence{ SessionView: *session.View(), LastInteracted: presence.Updated, }) if err != nil { rb() return fmt.Errorf("presence marshal error: %s", err) } if err := t.Insert(presence); err != nil { return fmt.Errorf("presence insert error: %s", err) } b.Lock() // Add session to listeners. lm, ok := b.listeners[room.Name] if !ok { lm = ListenerMap{} b.listeners[room.Name] = lm } lm[session.ID()] = Listener{Session: session, Client: client} b.Unlock() if err := room.broadcast(ctx, t, proto.JoinEventType, proto.PresenceEvent(*session.View()), session); err != nil { rb() return err } if err := t.Commit(); err != nil { return err } return nil }
func (b *Backend) join(ctx scope.Context, room *Room, session proto.Session) error { client := &proto.Client{} if !client.FromContext(ctx) { return fmt.Errorf("client data not found in scope") } // Check for agent ID bans. agentBans, err := b.DbMap.Select( BannedAgent{}, "SELECT agent_id, room, created, expires, room_reason, agent_reason, private_reason"+ " FROM banned_agent"+ " WHERE agent_id = $1 AND (room IS NULL OR room = $2)"+ " AND (expires IS NULL OR expires > NOW())", session.Identity().ID().String(), room.Name) if err != nil { return err } if len(agentBans) > 0 { backend.Logger(ctx).Printf("access denied to %s: %#v", session.Identity().ID(), agentBans) return proto.ErrAccessDenied } // Check for IP bans. ipBans, err := b.DbMap.Select( BannedIP{}, "SELECT ip, room, created, expires, reason FROM banned_ip"+ " WHERE ip = $1 AND (room IS NULL OR room = $2)"+ " AND (expires IS NULL OR expires > NOW())", client.IP, room.Name) if err != nil { return err } if len(ipBans) > 0 { backend.Logger(ctx).Printf("access denied to %s: %#v", client.IP, ipBans) return proto.ErrAccessDenied } // Write to session log. entry := &SessionLog{ SessionID: session.ID(), IP: client.IP, Room: room.Name, UserAgent: client.UserAgent, Connected: client.Connected, } t, err := b.DbMap.Begin() if err != nil { return err } if _, err := t.Delete(entry); err != nil { if rerr := t.Rollback(); rerr != nil { backend.Logger(ctx).Printf("rollback error: %s", rerr) } return err } if err := t.Insert(entry); err != nil { if rerr := t.Rollback(); rerr != nil { backend.Logger(ctx).Printf("rollback error: %s", rerr) } return err } if err := t.Commit(); err != nil { return err } b.Lock() defer b.Unlock() // Add session to listeners. lm, ok := b.listeners[room.Name] if !ok { b.debug("registering listener for %s", room.Name) lm = ListenerMap{} b.listeners[room.Name] = lm } lm[session.ID()] = Listener{Session: session, Client: client} // Broadcast a presence event. // TODO: make this an explicit action via the Room protocol, to support encryption presence := &Presence{ Room: room.Name, ServerID: b.desc.ID, ServerEra: b.desc.Era, SessionID: session.ID(), Updated: time.Now(), } err = presence.SetFact(&proto.Presence{ SessionView: *session.View(), LastInteracted: presence.Updated, }) if err != nil { return fmt.Errorf("presence marshal error: %s", err) } if err := b.DbMap.Insert(presence); err != nil { return fmt.Errorf("presence insert error: %s", err) } b.debug("joining session: %#v", session) b.debug(" -> %#v", session.View()) return b.broadcast(ctx, room, proto.JoinEventType, proto.PresenceEvent(*session.View()), session) }