Browse Source

Looks like 2FA works in facebook messenger :o

master
Alex 4 months ago
parent
commit
07f9f640f0
7 changed files with 144 additions and 42 deletions
  1. +4
    -0
      connector/connector.go
  2. +12
    -1
      connector/external/external.go
  3. +4
    -0
      connector/irc/irc.go
  4. +4
    -0
      connector/mattermost/mattermost.go
  5. +4
    -0
      connector/xmpp/xmpp.go
  6. +103
    -41
      external/messenger.py
  7. +13
    -0
      server.go

+ 4
- 0
connector/connector.go View File

@ -71,6 +71,10 @@ type Connector interface {
// in which case the backend is free to re-use the ID or select a new one.
Send(event *Event) (string, error)
// Used to send user commands directly
// (first use case: receive 2-factor authentication codes)
UserCommand(string)
// Close the connection
Close()
}

+ 12
- 1
connector/external/external.go View File

@ -51,10 +51,12 @@ const (
LEAVE = "leave"
SEARCH = "search"
SEND = "send"
USER_COMMAND = "user_command"
CLOSE = "close"
// external -> ezbr
SAVE_CONFIG = "save_config"
SYSTEM_MESSAGE = "system_message"
JOINED = "joined"
LEFT = "left"
USER_INFO_UPDATED = "user_info_updated"
@ -249,7 +251,7 @@ func (m *extMessageWithData) UnmarshalJSON(jj []byte) error {
}
m.Data = sr.Data
return nil
case JOINED, LEFT, CACHE_PUT, CACHE_GET, REP_OK, REP_ERROR:
case SYSTEM_MESSAGE, JOINED, LEFT, CACHE_PUT, CACHE_GET, REP_OK, REP_ERROR:
return nil
default:
return fmt.Errorf("Invalid message type for message from external program: '%s'", c.MsgType)
@ -377,6 +379,8 @@ func (ext *External) handleCmd(msg *extMessageWithData) {
switch msg.MsgType {
case SAVE_CONFIG:
ext.handler.SaveConfig(msg.Data.(Configuration))
case SYSTEM_MESSAGE:
ext.handler.SystemMessage(msg.Value)
case JOINED:
ext.handler.Joined(msg.Room)
case LEFT:
@ -475,3 +479,10 @@ func (ext *External) Send(event *Event) (string, error) {
}
return rep.EventId, nil
}
func (ext *External) UserCommand(cm string) {
ext.cmd(extMessage{
MsgType: USER_COMMAND,
Value: cm,
}, nil)
}

+ 4
- 0
connector/irc/irc.go View File

@ -271,6 +271,10 @@ func (irc *IRC) Send(event *Event) (string, error) {
return "", nil
}
func (irc *IRC) UserCommand(cm string) {
irc.handler.SystemMessage("Command not supported.")
}
func (irc *IRC) Close() {
conn := irc.conn
irc.conn = nil

+ 4
- 0
connector/mattermost/mattermost.go View File

@ -334,6 +334,10 @@ func (mm *Mattermost) Send(event *Event) (string, error) {
return created_post.Id, nil
}
func (mm *Mattermost) UserCommand(cm string) {
mm.handler.SystemMessage("Command not supported.")
}
func (mm *Mattermost) Close() {
if mm.conn != nil {
mm.conn.WsQuit = true

+ 4
- 0
connector/xmpp/xmpp.go View File

@ -367,6 +367,10 @@ func (xm *XMPP) Send(event *Event) (string, error) {
}
}
func (xm *XMPP) UserCommand(cmd string) {
xm.handler.SystemMessage("Command not supported.")
}
func (xm *XMPP) Close() {
if xm.conn != nil {
xm.conn.Close()

+ 103
- 41
external/messenger.py View File

@ -29,10 +29,12 @@ INVITE = "invite"
LEAVE = "leave"
SEARCH = "search"
SEND = "send"
USER_COMMAND = "user_command"
CLOSE = "close"
# external -> ezbr
SAVE_CONFIG = "save_config"
SYSTEM_MESSAGE = "system_message"
JOINED = "joined"
LEFT = "left"
USER_INFO_UPDATED = "user_info_updated"
@ -71,9 +73,9 @@ def stripFbLinkPrefix(url):
# ---- MESSENGER CLIENT CLASS THAT HANDLES EVENTS ----
class MessengerBridgeClient(fbchat.Client):
def __init__(self, *args, **kwargs):
def __init__(self, bridge, *args, **kwargs):
self.bridge = bridge
super(MessengerBridgeClient, self).__init__(*args, **kwargs)
self.bridge = None
def setBridge(self, bridge):
self.bridge = bridge
@ -87,9 +89,20 @@ class MessengerBridgeClient(fbchat.Client):
self.bridge.onPersonRemoved(*args, **kwargs)
def onTitleChange(self, *args, **kwargs):
self.bridge.onTitleChange(*args, **kwargs)
def on2FACode(self, *args, **kwargs):
return self.bridge.on2FACode(*args, **kwargs)
# ---- SEPARATE THREADS FOR INITIAL SYNC & CLIENT LISTEN ----
class LoginThread(threading.Thread):
def __init__(self, bridge, *args, **kwargs):
super(LoginThread, self).__init__(*args, **kwargs)
self.bridge = bridge
def run(self):
self.bridge.processLogin()
class SyncerThread(threading.Thread):
def __init__(self, bridge, thread_queue, *args, **kwargs):
super(SyncerThread, self).__init__(*args, **kwargs)
@ -121,6 +134,9 @@ class MessengerBridge:
def __init__(self):
self.init_backlog_length = 100
self.config = None
self.login_in_progress = None
# We cache maps between two kinds of identifiers:
# - facebook uids of users
# - identifiers for the bridge, which are the username when defined (otherwise equal to above)
@ -135,6 +151,9 @@ class MessengerBridge:
# caches for the people that are in rooms so that we don't send JOINED every time (map keys = "<userId>--<threadId>")
self.others_joined_map = {}
# queue for thread syncing
self.sync_thread_queue = queue.Queue(100)
def getUserId(self, user):
retval = None
if user.url is not None and not "?" in user.url:
@ -222,48 +241,21 @@ class MessengerBridge:
sys.stdout.write(msgstr + "\n")
sys.stdout.flush()
def system_message(self, msg):
self.write({
"_type": SYSTEM_MESSAGE,
"value": msg,
})
def handle_cmd(self, cmd):
ty = cmd["_type"]
if ty == CONFIGURE:
self.init_backlog_length = int(cmd["data"]["initial_backlog"])
has_pickle = "client_pickle" in cmd["data"] and len(cmd["data"]["client_pickle"]) > 0
if has_pickle:
data = base64.b64decode(cmd["data"]["client_pickle"])
data = zlib.decompress(data)
self.client = pickle.loads(data)
if self.login_in_progress is None:
self.config = cmd["data"]
self.login_in_progress = queue.Queue(1)
LoginThread(self).start()
else:
email, password = cmd["data"]["email"], cmd["data"]["password"]
self.client = MessengerBridgeClient(email=email, password=password, max_tries=1)
## TODO: save client in new client_pickle config value
if not self.client.isLoggedIn():
return {"_type": REP_ERROR, "error": "Unable to login (invalid pickle?)"}
if not has_pickle:
new_config = cmd["data"]
data = pickle.dumps(self.client)
data = zlib.compress(data)
new_config["client_pickle"] = base64.b64encode(data).decode('ascii')
self.write({"_type": SAVE_CONFIG, "data": new_config})
self.client.setBridge(self)
self.my_user_id = self.getUserIdFromUid(self.client.uid)
threads = self.client.fetchThreadList(limit=10)
# ensure we have a correct mapping for bridged user IDs to fb uids
# (this should be fast)
for thread in threads:
if thread.type == ThreadType.USER:
self.getUserId(thread)
self.sync_thread_queue = queue.Queue(100)
SyncerThread(self, self.sync_thread_queue).start()
for thread in reversed(threads):
self.sync_thread_queue.put(thread)
ClientListenThread(self.client).start()
return {"_type": REP_ERROR, "error": "Already logging in (CONFIGURE sent twice)"}
elif ty == CLOSE:
self.close()
@ -272,7 +264,10 @@ class MessengerBridge:
return {"_type": REP_OK, "user": self.my_user_id}
elif ty == JOIN:
self.ensure_i_joined(cmd["room"])
if self.client is None:
pass
else:
self.ensure_i_joined(cmd["room"])
elif ty == LEAVE:
thread_id = cmd["room"]
@ -331,6 +326,9 @@ class MessengerBridge:
elif ty == REP_OK and cmd["_id"] in self.cache_gets:
self.cache_gets[cmd["_id"]].put(cmd["value"])
elif ty == USER_COMMAND:
self.handleUserCommand(cmd["value"])
else:
return {"_type": REP_ERROR, "error": "Not implemented"}
@ -354,6 +352,51 @@ class MessengerBridge:
def cache_put(self, key, value):
self.write({"_type": CACHE_PUT, "key": key, "value": value})
# ---- Process login (called from separate thread) ----
def processLogin(self):
self.init_backlog_length = int(self.config["initial_backlog"])
has_pickle = "client_pickle" in self.config and len(self.config["client_pickle"]) > 0
if has_pickle:
data = base64.b64decode(self.config["client_pickle"])
data = zlib.decompress(data)
self.client = pickle.loads(data)
else:
email, password = self.config["email"], self.config["password"]
self.client = MessengerBridgeClient(bridge=self, email=email, password=password, max_tries=1)
if not self.client.isLoggedIn():
self.system_message("Unable to login (invalid pickle? dunno)")
else:
self.system_message("Login complete, will now sync threads.")
if not has_pickle:
self.client.setBridge(None)
data = pickle.dumps(self.client)
data = zlib.compress(data)
self.config["client_pickle"] = base64.b64encode(data).decode('ascii')
self.write({"_type": SAVE_CONFIG, "data": self.config})
self.client.setBridge(self)
self.my_user_id = self.getUserIdFromUid(self.client.uid)
threads = self.client.fetchThreadList(limit=10)
# ensure we have a correct mapping for bridged user IDs to fb uids
# (this should be fast)
for thread in threads:
if thread.type == ThreadType.USER:
self.getUserId(thread)
SyncerThread(self, self.sync_thread_queue).start()
for thread in reversed(threads):
self.sync_thread_queue.put(thread)
ClientListenThread(self.client).start()
self.login_in_progress = None
# ---- Info sync ----
def ensure_i_joined(self, thread_id):
@ -566,6 +609,25 @@ class MessengerBridge:
"data": {"name": new_title},
})
def on2FACode(self, *args, **kwargs):
if self.login_in_progress is None:
self.system_message("Facebook messenger requests 2 factor authentication, but we have a bug so that won't work.")
return None
else:
self.system_message("Facebook messenger requests 2 factor authentication. Enter it by saying: cmd messenger 2fa <your code> (replace messenger by your account name if you have several messenger accounts)")
uc = self.login_in_progress.get(block=True)
return uc["2fa_code"]
def handleUserCommand(self, cmd):
cmd = cmd.split(' ')
if cmd[0] == "2fa":
if self.login_in_progress is not None:
self.login_in_progress.put({"2fa_code": cmd[1]})
else:
self.system_message("2FA code not required at this point.")
else:
self.system_message("Invalid user command.")
# ---- CLI ----
def createClientPickle():

+ 13
- 0
server.go View File

@ -259,6 +259,7 @@ func handleSystemMessage(mxid string, msg string) {
ezbrSystemSend(mxid, "- join <protocol or account> <room id>: join public chat room")
ezbrSystemSend(mxid, "- talk <protocol or account> <user id>: open private conversation to contact")
ezbrSystemSend(mxid, "- search <protocol or account> <name>: search for users by name")
ezbrSystemSend(mxid, "- cmd <protocol or account> <command>: send special command to account")
case "list", "account", "accounts":
one := false
if accts, ok := registeredAccounts[mxid]; ok {
@ -332,6 +333,18 @@ func handleSystemMessage(mxid string, msg string) {
} else {
ezbrSystemSendf(mxid, "No account with name or using protocol %s", cmd[1])
}
case "cmd":
if len(cmd) < 3 {
ezbrSystemSendf(mxid, "Usage: %s <protocol or account> <name>", cmd[0])
return
}
account := findAccount(mxid, cmd[1])
if account != nil {
account.Conn.UserCommand(strings.Join(cmd[2:], " "))
} else {
ezbrSystemSendf(mxid, "No account with name or using protocol %s", cmd[1])
}
default:
ezbrSystemSend(mxid, "Unrecognized command. Type `help` if you need some help!")
}

Loading…
Cancel
Save