From a76bcdcbcafd855964ced361ce12aa69dd660671 Mon Sep 17 00:00:00 2001 From: Abyr Valg Date: Tue, 8 Aug 2017 15:40:28 +0300 Subject: Document the unknowns --- facebook/facebook-api.c | 29 +++++++++++++++++------------ facebook/facebook-api.h | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/facebook/facebook-api.c b/facebook/facebook-api.c index fb0581d..6783ba4 100644 --- a/facebook/facebook-api.c +++ b/facebook/facebook-api.c @@ -922,23 +922,26 @@ fb_api_cb_mqtt_open(FbMqtt *mqtt, gpointer data) ? fb_api_get_agent_string(priv->tweak, 1) : FB_API_MQTT_AGENT); - /* Write the UNKNOWN ("cp"?) */ + /* Write the client capabilities */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 3, 2); - fb_thrift_write_i64(thft, 23); + fb_thrift_write_i64(thft, FB_CP_ACKNOWLEDGED_DELIVERY | + FB_CP_PROCESSING_LASTACTIVE_PRESENCEINFO | + FB_CP_EXACT_KEEPALIVE | + FB_CP_DELTA_SENT_MESSAGE_ENABLED); - /* Write the UNKNOWN ("ecp"?) */ + /* Write the endpoint capabilitites */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 4, 3); fb_thrift_write_i64(thft, 26); - /* Write the UNKNOWN */ + /* Write the publish payload format (deflate) */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 5, 4); fb_thrift_write_i32(thft, 1); - /* Write the UNKNOWN ("no_auto_fg"?) */ + /* Write the noAutomaticForeground flag */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 6, 5); fb_thrift_write_bool(thft, TRUE); - /* Write the visibility state */ + /* Write the visibility state (makeUserAvailableInForeground flag) */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 7, 6); fb_thrift_write_bool(thft, !priv->invisible); @@ -946,15 +949,15 @@ fb_api_cb_mqtt_open(FbMqtt *mqtt, gpointer data) fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 8, 7); fb_thrift_write_str(thft, priv->did); - /* Write the UNKNOWN ("fg"?) */ + /* Write the isInitiallyForeground flag */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 9, 8); fb_thrift_write_bool(thft, TRUE); - /* Write the UNKNOWN ("nwt"?) */ + /* Write the network type (WIFI) */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 10, 9); fb_thrift_write_i32(thft, 1); - /* Write the UNKNOWN ("nwst"?) */ + /* Write the network subtype (none) */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 11, 10); fb_thrift_write_i32(thft, 0); @@ -962,16 +965,18 @@ fb_api_cb_mqtt_open(FbMqtt *mqtt, gpointer data) fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 12, 11); fb_thrift_write_i64(thft, priv->mid); - /* Write the UNKNOWN */ + /* Write the list of topics to subscribe */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_LIST, 14, 12); fb_thrift_write_list(thft, FB_THRIFT_TYPE_I32, 0); + + /* Write the STOP for the struct */ fb_thrift_write_stop(thft); /* Write the token */ - fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 15, 14); + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 5, 4); fb_thrift_write_str(thft, priv->token); - /* Write the STOP for the struct */ + /* Write the STOP for the message */ fb_thrift_write_stop(thft); bytes = fb_thrift_get_bytes(thft); diff --git a/facebook/facebook-api.h b/facebook/facebook-api.h index 3ed0e41..621800f 100644 --- a/facebook/facebook-api.h +++ b/facebook/facebook-api.h @@ -446,6 +446,39 @@ typedef enum FB_API_MESSAGE_FLAG_SELF = 1 << 2 } FbApiMessageFlags; +/** +* FbApiClientCapabilities: +* @FB_CP_ACKNOWLEDGED_DELIVERY: +* @FB_CP_PROCESSING_LASTACTIVE_PRESENCEINFO: +* @FB_CP_EXACT_KEEPALIVE: +* @FB_CP_REQUIRES_JSON_UNICODE_ESCAPES: +* @FB_CP_DELTA_SENT_MESSAGE_ENABLED: +* @FB_CP_USE_ENUM_TOPIC: All topics are numeric. +* @FB_CP_SUPPRESS_GETDIFF_IN_CONNECT: +* @FB_CP_USE_THRIFT_FOR_INBOX: +* @FB_CP_USE_SEND_PINGRESP: +* @FB_CP_REQUIRE_REPLAY_PROTECTION: +* @FB_CP_DATA_SAVING_MODE: +* @FB_CP_TYPING_OFF_WHEN_SENDING_MESSAGE: +* +* The client capabilities. +*/ +typedef enum +{ + FB_CP_ACKNOWLEDGED_DELIVERY = 1 << 0, + FB_CP_PROCESSING_LASTACTIVE_PRESENCEINFO = 1 << 1, + FB_CP_EXACT_KEEPALIVE = 1 << 2, + FB_CP_REQUIRES_JSON_UNICODE_ESCAPES = 1 << 3, + FB_CP_DELTA_SENT_MESSAGE_ENABLED = 1 << 4, + FB_CP_USE_ENUM_TOPIC = 1 << 5, + FB_CP_SUPPRESS_GETDIFF_IN_CONNECT = 1 << 6, + FB_CP_USE_THRIFT_FOR_INBOX = 1 << 7, + FB_CP_USE_SEND_PINGRESP = 1 << 8, + FB_CP_REQUIRE_REPLAY_PROTECTION = 1 << 9, + FB_CP_DATA_SAVING_MODE = 1 << 10, + FB_CP_TYPING_OFF_WHEN_SENDING_MESSAGE = 1 << 11 +} FbApiClientCapabilities; + /** * FbApi: * -- cgit v1.2.3 From 7dbd24d7e29c421701b43d8e8a4ffc5c9e1ecbf5 Mon Sep 17 00:00:00 2001 From: Robert Scheck Date: Sun, 1 Oct 2017 21:08:54 +0200 Subject: =?UTF-8?q?Replace=20obsolete=20m4=20macros,=20thanks=20to=20Rober?= =?UTF-8?q?t-Andr=C3=A9=20Mauchin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - https://bugzilla.redhat.com/show_bug.cgi?id=1290235#c3 - https://www.gnu.org/software/libtool/manual/html_node/LT_005fINIT.html --- configure.ac | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index fc31a95..d4f8171 100644 --- a/configure.ac +++ b/configure.ac @@ -29,8 +29,7 @@ AM_INIT_AUTOMAKE([no-define]) AC_PROG_CC AM_PROG_CC_C_O -AC_DISABLE_STATIC -AC_PROG_LIBTOOL +LT_INIT([disable-static]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) m4_ifdef([AC_PROG_CC_C99], [AC_PROG_CC_C99]) -- cgit v1.2.3 From 1e264442a48f33a5933c49a2c0332e426dcdb4a1 Mon Sep 17 00:00:00 2001 From: dequis Date: Mon, 16 Oct 2017 22:10:30 -0300 Subject: README: mention deps --- README | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README b/README index 007b77d..39ed536 100644 --- a/README +++ b/README @@ -17,18 +17,21 @@ https://jgeboski.github.io/ ## Building from source +The folowing packages are required: autoconf, automake, libtool, glib2, +json-glib, bitlbee (names may vary across distros) + +Example for debian-based systems: + + apt install build-essential autoconf automake libtool libglib2.0-dev libjson-glib-dev bitlbee-dev + Make sure bitlbee and its headers have been installed. If bitlbee came from the distribution's repository, it will most likely need the -development package, usually bitlbee-dev. +development package, like bitlbee-dev in the example above. If bitlbee was built by hand (or alike via a script), ensure the make target `install-dev` is invoked. This target is not called by default, and will install the headers that are needed. -Do *not* use the source tree headers unless you know what you are -doing. This can lead to mismatched header versions, which often times -will lead to bad things. - $ git clone https://github.com/bitlbee/bitlbee-facebook.git $ cd bitlbee-facebook -- cgit v1.2.3 From 24db488909604dd389b584c1f1ce43c549648dbe Mon Sep 17 00:00:00 2001 From: dequis Date: Mon, 16 Oct 2017 22:11:42 -0300 Subject: Work chat login support (enable the "work" setting to use it) This covers three autodetected login types: 1. Work account password Simple, very similar to normal account types 2. Linked personal account This is deprecated but still needed in some companies. Looks just like password auth to users. In rare cases there may be more than one work account linked to a personal account, in which case this will only use the first one. Usually they can be de-linked by assigning a password (see the official docs) 3. SSO This one is awkward. The password can be set to garbage and users will receive a PM with instructions to do an oauth-like login, but there's no explicit auth code screen, just a redirect to a fb-workchat-sso://, which probably results in an error. Users are expected to copy that url, hopefully from the address bar. Not very practical, but works! In all cases, the username is the work account email. --- facebook/facebook-api.c | 280 +++++++++++++++++++++++++++++++++++++++++++++-- facebook/facebook-api.h | 80 +++++++++++++- facebook/facebook-util.c | 43 ++++++++ facebook/facebook-util.h | 26 +++++ facebook/facebook.c | 47 +++++++- 5 files changed, 467 insertions(+), 9 deletions(-) diff --git a/facebook/facebook-api.c b/facebook/facebook-api.c index 6783ba4..27a8aab 100644 --- a/facebook/facebook-api.c +++ b/facebook/facebook-api.c @@ -27,6 +27,7 @@ #include "facebook-util.h" typedef struct _FbApiData FbApiData; +typedef struct _FbApiPreloginData FbApiPreloginData; enum { @@ -64,6 +65,10 @@ struct _FbApiPrivate FbId lastmid; gchar *contacts_delta; int tweak; + gboolean is_work; + gboolean need_work_switch; + gchar *sso_verifier; + FbId work_community_id; }; struct _FbApiData @@ -72,6 +77,13 @@ struct _FbApiData GDestroyNotify func; }; +struct _FbApiPreloginData +{ + FbApi *api; + gchar *user; + gchar *pass; +}; + static void fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg); @@ -209,6 +221,7 @@ fb_api_dispose(GObject *obj) g_free(priv->stoken); g_free(priv->token); g_free(priv->contacts_delta); + g_free(priv->sso_verifier); } static void @@ -546,6 +559,22 @@ fb_api_class_init(FbApiClass *klass) fb_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * FbApi::work-sso-login: + * @api: The #FbApi. + * + * Emitted when user interaction is required to continue SAML SSO login + */ + + g_signal_new("work-sso-login", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); } static void @@ -764,7 +793,8 @@ fb_api_http_req(FbApi *api, const gchar *url, const gchar *name, GList *l; GString *gstr; - fb_http_values_set_str(values, "api_key", FB_API_KEY); + fb_http_values_set_str(values, "api_key", + priv->is_work ? FB_WORK_API_KEY : FB_API_KEY); fb_http_values_set_str(values, "device_id", priv->did); fb_http_values_set_str(values, "fb_api_req_friendly_name", name); fb_http_values_set_str(values, "format", "json"); @@ -787,7 +817,7 @@ fb_api_http_req(FbApi *api, const gchar *url, const gchar *name, g_string_append_printf(gstr, "%s=%s", key, val); } - g_string_append(gstr, FB_API_SECRET); + g_string_append(gstr, priv->is_work ? FB_WORK_API_SECRET : FB_API_SECRET); data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, gstr->len); fb_http_values_set_str(values, "sig", data); @@ -2108,6 +2138,53 @@ fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg) fb_api_data_set(api, req, msg, (GDestroyNotify) fb_api_message_free); } +static void +fb_api_cb_work_peek(FbHttpRequest *req, gpointer data) +{ + FbApi *api = data; + FbApiPrivate *priv = api->priv; + GError *err = NULL; + JsonNode *root; + gchar *community = NULL; + + if (!fb_api_http_chk(api, req, &root)) { + return; + } + + /* The work_users[0] explicitly only handles the first user. + * If more than one user is ever needed, this is what you want to change, + * but as far as I know this feature (linked work accounts) is deprecated + * and most users can detach their work accounts from their personal + * accounts by assigning a password to the work account. */ + community = fb_json_node_get_str(root, + "$.data.viewer.work_users[0].community.login_identifier", &err); + + FB_API_ERROR_EMIT(api, err, + g_free(community); + json_node_free(root); + return; + ); + + priv->work_community_id = FB_ID_FROM_STR(community); + + fb_api_auth(api, "X", "X", "personal_to_work_switch"); + + g_free(community); + json_node_free(root); +} + +static FbHttpRequest * +fb_api_work_peek(FbApi *api) +{ + FbHttpValues *prms; + + prms = fb_http_values_new(); + fb_http_values_set_int(prms, "doc_id", FB_API_WORK_COMMUNITY_PEEK); + + return fb_api_http_req(api, FB_API_URL_GQL, "WorkCommunityPeekQuery", + "post", prms, fb_api_cb_work_peek); +} + static void fb_api_cb_auth(FbHttpRequest *req, gpointer data) { @@ -2123,7 +2200,14 @@ fb_api_cb_auth(FbHttpRequest *req, gpointer data) values = fb_json_values_new(root); fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.access_token"); - fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid"); + + /* extremely silly difference */ + if (priv->is_work) { + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.uid"); + } else { + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid"); + } + fb_json_values_update(values, &err); FB_API_ERROR_EMIT(api, err, @@ -2134,25 +2218,202 @@ fb_api_cb_auth(FbHttpRequest *req, gpointer data) g_free(priv->token); priv->token = fb_json_values_next_str_dup(values, NULL); - priv->uid = fb_json_values_next_int(values, 0); - g_signal_emit_by_name(api, "auth"); + if (priv->is_work) { + priv->uid = FB_ID_FROM_STR(fb_json_values_next_str(values, "0")); + } else { + priv->uid = fb_json_values_next_int(values, 0); + } + + if (priv->need_work_switch) { + fb_api_work_peek(api); + priv->need_work_switch = FALSE; + } else { + g_signal_emit_by_name(api, "auth"); + } + g_object_unref(values); json_node_free(root); } void -fb_api_auth(FbApi *api, const gchar *user, const gchar *pass) +fb_api_auth(FbApi *api, const gchar *user, const gchar *pass, const gchar *credentials_type) { + FbApiPrivate *priv = api->priv; FbHttpValues *prms; prms = fb_http_values_new(); fb_http_values_set_str(prms, "email", user); fb_http_values_set_str(prms, "password", pass); + + if (credentials_type) { + fb_http_values_set_str(prms, "credentials_type", credentials_type); + } + + if (priv->sso_verifier) { + fb_http_values_set_str(prms, "code_verifier", priv->sso_verifier); + g_free(priv->sso_verifier); + priv->sso_verifier = NULL; + } + + if (priv->work_community_id) { + fb_http_values_set_int(prms, "community_id", priv->work_community_id); + } + + if (priv->is_work && priv->token) { + fb_http_values_set_str(prms, "access_token", priv->token); + } + fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login", prms, fb_api_cb_auth); } +static void +fb_api_cb_work_prelogin(FbHttpRequest *req, gpointer data) +{ + FbApiPreloginData *pata = data; + FbApi *api = pata->api; + FbApiPrivate *priv = api->priv; + GError *err = NULL; + JsonNode *root; + gchar *status; + gchar *user = pata->user; + gchar *pass = pata->pass; + + g_free(pata); + + if (!fb_api_http_chk(api, req, &root)) { + return; + } + + status = fb_json_node_get_str(root, "$.status", &err); + + FB_API_ERROR_EMIT(api, err, + json_node_free(root); + return; + ); + + if (g_strcmp0(status, "can_login_password") == 0) { + fb_api_auth(api, user, pass, "work_account_password"); + + } else if (g_strcmp0(status, "can_login_via_linked_account") == 0) { + fb_api_auth(api, user, pass, "personal_account_password_with_work_username"); + priv->need_work_switch = TRUE; + + } else if (g_strcmp0(status, "can_login_sso") == 0) { + g_signal_emit_by_name(api, "work-sso-login"); + + } else if (g_strcmp0(status, "cannot_login") == 0) { + char *reason = fb_json_node_get_str(root, "$.cannot_login_reason", NULL); + + if (g_strcmp0(reason, "non_business_email") == 0) { + fb_api_error(api, FB_API_ERROR_AUTH, + "Cannot login with non-business email. " + "Change the 'username' setting or disable 'work'"); + } else { + char *title = fb_json_node_get_str(root, "$.error_title", NULL); + char *body = fb_json_node_get_str(root, "$.error_body", NULL); + + fb_api_error(api, FB_API_ERROR_AUTH, + "Work prelogin failed (%s - %s)", title, body); + + g_free(title); + g_free(body); + } + + g_free(reason); + + } else if (g_strcmp0(status, "can_self_invite") == 0) { + fb_api_error(api, FB_API_ERROR_AUTH, "Unknown email. " + "Change the 'username' setting or disable 'work'"); + } + + g_free(status); + json_node_free(root); +} + +void +fb_api_work_login(FbApi *api, gchar *user, gchar *pass) +{ + FbApiPrivate *priv = api->priv; + FbHttpRequest *req; + FbHttpValues *prms, *hdrs; + FbApiPreloginData *pata = g_new0(FbApiPreloginData, 1); + + pata->api = api; + pata->user = user; + pata->pass = pass; + + priv->is_work = TRUE; + + req = fb_http_request_new(priv->http, FB_API_URL_WORK_PRELOGIN, TRUE, + fb_api_cb_work_prelogin, pata); + + hdrs = fb_http_request_get_headers(req); + fb_http_values_set_str(hdrs, "Authorization", "OAuth null"); + + prms = fb_http_request_get_params(req); + fb_http_values_set_str(prms, "email", user); + fb_http_values_set_str(prms, "access_token", + FB_WORK_API_KEY "|" FB_WORK_API_SECRET); + + fb_http_request_send(req); +} + +gchar * +fb_api_work_gen_sso_url(FbApi *api, const gchar *user) +{ + FbApiPrivate *priv = api->priv; + gchar *challenge, *verifier, *req_id, *email; + gchar *ret; + + fb_util_gen_sso_verifier(&challenge, &verifier, &req_id); + + email = g_uri_escape_string(user, NULL, FALSE); + + ret = g_strdup_printf(FB_API_SSO_URL, req_id, challenge, email); + + g_free(req_id); + g_free(challenge); + g_free(email); + + g_free(priv->sso_verifier); + priv->sso_verifier = verifier; + + return ret; +} + +void +fb_api_work_got_nonce(FbApi *api, const gchar *url) +{ + gchar **split; + gchar *uid = NULL; + gchar *nonce = NULL; + int i; + + if (!g_str_has_prefix(url, "fb-workchat-sso://sso/?")) { + return; + } + + split = g_strsplit(strchr(url, '?'), "&", -1); + + for (i = 0; split[i]; i++) { + gchar *eq = strchr(split[i], '='); + + if (g_str_has_prefix(split[i], "uid=")) { + uid = g_strstrip(eq + 1); + } else if (g_str_has_prefix(split[i], "nonce=")) { + nonce = g_strstrip(eq + 1); + } + } + + if (uid && nonce) { + fb_api_auth(api, uid, nonce, "work_sso_nonce"); + } + + g_strfreev(split); +} + static gchar * fb_api_user_icon_checksum(gchar *icon) { @@ -2257,6 +2518,8 @@ fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users) "$.represented_profile.id"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.represented_profile.friendship_status"); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, + "$.is_on_viewer_contact_list"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.structured_name.text"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, @@ -2269,11 +2532,14 @@ fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users) } while (fb_json_values_update(values, &err)) { + gboolean in_contact_list; + str = fb_json_values_next_str(values, "0"); uid = FB_ID_FROM_STR(str); str = fb_json_values_next_str(values, NULL); + in_contact_list = fb_json_values_next_bool(values, FALSE); - if (((g_strcmp0(str, "ARE_FRIENDS") != 0) && + if ((!in_contact_list && (g_strcmp0(str, "ARE_FRIENDS") != 0) && (uid != priv->uid)) || (uid == 0)) { if (!is_array) { diff --git a/facebook/facebook-api.h b/facebook/facebook-api.h index 621800f..2e63471 100644 --- a/facebook/facebook-api.h +++ b/facebook/facebook-api.h @@ -88,6 +88,20 @@ */ #define FB_API_SECRET "374e60f8b9bb6b8cbb30f78030438895" +/** + * FB_WORK_API_KEY: + * + * The Facebook workchat app API key. + */ +#define FB_WORK_API_KEY "312713275593566" + +/** + * FB_WORK_API_SECRET: + * + * The Facebook workchat app API secret. + */ +#define FB_WORK_API_SECRET "d2901dc6cb685df3b074b30b56b78d28" + /** * FB_ORCA_AGENT * @@ -137,6 +151,15 @@ */ #define FB_API_URL_AUTH FB_API_BHOST "/method/auth.login" +/** + * FB_API_URL_WORK_PRELOGIN + * + * The URL for workchat pre-login information, indicating what auth method + * should be used + */ + +#define FB_API_URL_WORK_PRELOGIN FB_API_GHOST "/at_work/pre_login_info" + /** * FB_API_URL_GQL: * @@ -172,6 +195,14 @@ */ #define FB_API_URL_TOPIC FB_API_AHOST "/method/messaging.setthreadname" +/** + * FB_API_SSO_URL: + * + * Template for the URL shown to workchat users when trying to authenticate + * with SSO. + */ +#define FB_API_SSO_URL "https://m.facebook.com/work/sso/mobile?app_id=312713275593566&response_url=fb-workchat-sso%%3A%%2F%%2Fsso&request_id=%s&code_challenge=%s&email=%s" + /** * FB_API_QUERY_CONTACT: * @@ -319,6 +350,16 @@ */ #define FB_API_QUERY_XMA 10153919431161729 +/** + * FB_API_WORK_COMMUNITY_PEEK: + * + * The docid with information about the work community of the currently + * authenticated user. + * + * Used when prelogin returns can_login_via_linked_account + */ +#define FB_API_WORK_COMMUNITY_PEEK 1295334753880530 + /** * FB_API_CONTACTS_COUNT: * @@ -674,12 +715,49 @@ fb_api_error_emit(FbApi *api, GError *error); * @api: The #FbApi. * @user: The Facebook user name, email, or phone number. * @pass: The Facebook password. + * @credentials_type: Type of work account credentials, or NULL * * Sends an authentication request to Facebook. This will obtain * session information, which is required for all other requests. */ void -fb_api_auth(FbApi *api, const gchar *user, const gchar *pass); +fb_api_auth(FbApi *api, const gchar *user, const gchar *pass, const gchar *credentials_type); + +/** + * fb_api_work_login: + * @api: The #FbApi. + * @user: The Facebook user name, email, or phone number. + * @pass: The Facebook password. + * + * Starts the workchat login sequence. + */ +void +fb_api_work_login(FbApi *api, gchar *user, gchar *pass); + +/** + * fb_api_work_gen_sso_url: + * @api: The #FbApi. + * @user: The Facebook user email. + * + * Generates the URL to be shown to the user to get the SSO auth token. This + * url contains a challenge and the corresponding verifier is saved in the + * FbApi instance to be used later. + * + * Returns: a newly allocated string. + */ +gchar * +fb_api_work_gen_sso_url(FbApi *api, const gchar *user); + +/** + * fb_api_work_got_nonce: + * @api: The #FbApi. + * @url: The fb-workchat-sso:// URL as entered by the user + * + * Parses the fb-workchat-sso:// URL that the user got redirected to and + * continues with work_sso_nonce auth + */ +void +fb_api_work_got_nonce(FbApi *api, const gchar *url); /** * fb_api_contact: diff --git a/facebook/facebook-util.c b/facebook/facebook-util.c index 15c4d4a..e101abe 100644 --- a/facebook/facebook-util.c +++ b/facebook/facebook-util.c @@ -376,3 +376,46 @@ fb_util_zlib_inflate(const GByteArray *bytes, GError **error) g_object_unref(conv); return ret; } + +gchar * +fb_util_urlsafe_base64_encode(const guchar *data, gsize len) +{ + gchar *out = g_base64_encode(data, len); + gchar *c; + + for (c = out; *c; c++) { + if (*c == '+') { + *c = '-'; + } else if (*c == '/') { + *c = '_'; + } else if (*c == '=') { + *c = '\0'; + break; + } + } + + return out; +} + +void +fb_util_gen_sso_verifier(gchar **challenge, gchar **verifier, gchar **req_id) +{ + guint8 buf[32]; + GChecksum *gc; + gsize digest_len = sizeof buf; + + random_bytes(buf, sizeof buf); + + *verifier = fb_util_urlsafe_base64_encode(buf, sizeof buf); + + gc = g_checksum_new(G_CHECKSUM_SHA256); + g_checksum_update(gc, (guchar *) *verifier, -1); + g_checksum_get_digest(gc, buf, &digest_len); + g_checksum_free(gc); + + *challenge = fb_util_urlsafe_base64_encode(buf, sizeof buf); + + random_bytes(buf, 3); + + *req_id = fb_util_urlsafe_base64_encode(buf, 3); +} diff --git a/facebook/facebook-util.h b/facebook/facebook-util.h index a595cf3..b080eb4 100644 --- a/facebook/facebook-util.h +++ b/facebook/facebook-util.h @@ -289,4 +289,30 @@ fb_util_zlib_deflate(const GByteArray *bytes, GError **error); GByteArray * fb_util_zlib_inflate(const GByteArray *bytes, GError **error); +/** + * fb_util_urlsafe_base64_encode: + * @data: the binary data to encode. + * @len: the length of data + * + * Wrapper around g_base64_encode() which substitutes '-' instead of '+' + * and '_' instead of '/' and removes the padding + * + * Returns: A newly allocated string. + */ + +gchar * +fb_util_urlsafe_base64_encode(const guchar *data, gsize len); + +/** + * fb_util_gen_sso_verifier: + * @challenge: base64 of sha256 of verifier + * @verifier: base64 of random data + * @req_id: base64 of random data + * + * Generates the challenge/response parameters used for the workchat SSO auth. + * All parameters are output parameters. + */ +void +fb_util_gen_sso_verifier(gchar **challenge, gchar **verifier, gchar **req_id); + #endif /* _FACEBOOK_UTIL_H_ */ diff --git a/facebook/facebook.c b/facebook/facebook.c index 526ccfe..0ced73f 100644 --- a/facebook/facebook.c +++ b/facebook/facebook.c @@ -26,6 +26,8 @@ #define OPT_SELFMESSAGE 0 #endif +#define FB_SSO_HANDLE "facebook_sso_auth" + typedef enum { FB_PTRBIT_NEW_BUDDY, FB_PTRBIT_UNREAD_MSG @@ -138,6 +140,9 @@ fb_cb_api_auth(FbApi *api, gpointer data) ic = fb_data_get_connection(fata); + /* likely a no-op if not authing with SSO */ + imcb_remove_buddy(ic, FB_SSO_HANDLE, NULL); + imcb_log(ic, "Fetching contacts"); fb_data_save(fata); fb_api_contacts(api); @@ -692,6 +697,31 @@ fb_cb_api_typing(FbApi *api, FbApiTyping *typg, gpointer data) imcb_buddy_typing(ic, uid, flags); } +static void +fb_cb_api_work_sso_login(FbApi *api, gpointer data) +{ + FbData *fata = data; + struct im_connection *ic; + gchar *url; + + ic = fb_data_get_connection(fata); + + url = fb_api_work_gen_sso_url(api, ic->acc->user); + imcb_add_buddy(ic, FB_SSO_HANDLE, NULL); + + imcb_buddy_msg(ic, FB_SSO_HANDLE, "Open this URL in your browser to authenticate:", 0, 0); + imcb_buddy_msg(ic, FB_SSO_HANDLE, url, 0, 0); + imcb_buddy_msg(ic, FB_SSO_HANDLE, + "Respond to this message with the URL starting with 'fb-workchat-sso://' that it attempts to redirect to.", + 0, 0); + imcb_buddy_msg(ic, FB_SSO_HANDLE, + "If your browser says 'Address not understood' (like firefox), copy it from the address bar. " + "Otherwise you might have to right click -> view source in the last page and find it there. Good luck!", + 0, 0); + + g_free(url); +} + static char * fb_eval_open(struct set *set, char *value) { @@ -743,6 +773,7 @@ fb_init(account_t *acct) set_add(&acct->set, "mark_read_reply", "false", set_eval_bool, acct); set_add(&acct->set, "show_unread", "false", set_eval_bool, acct); set_add(&acct->set, "sync_interval", "5", set_eval_int, acct); + set_add(&acct->set, "work", "false", set_eval_bool, acct); } static void @@ -813,10 +844,18 @@ fb_login(account_t *acc) "typing", G_CALLBACK(fb_cb_api_typing), fata); + g_signal_connect(api, + "work-sso-login", + G_CALLBACK(fb_cb_api_work_sso_login), + fata); if (!fb_data_load(fata)) { imcb_log(ic, "Authenticating"); - fb_api_auth(api, acc->user, acc->pass); + if (set_getbool(&acc->set, "work")) { + fb_api_work_login(api, acc->user, acc->pass); + } else { + fb_api_auth(api, acc->user, acc->pass, NULL); + } return; } @@ -848,6 +887,12 @@ fb_buddy_msg(struct im_connection *ic, char *to, char *message, int flags) FbId uid; api = fb_data_get_api(fata); + + if (g_strcmp0(to, FB_SSO_HANDLE) == 0 && !(ic->flags & OPT_LOGGED_IN)) { + fb_api_work_got_nonce(api, message); + return 0; + } + uid = FB_ID_FROM_STR(to); bu = bee_user_by_handle(ic->bee, ic, to); -- cgit v1.2.3 From 553593d07170d6d1563d0079dbedd481dcec5b00 Mon Sep 17 00:00:00 2001 From: dequis Date: Tue, 17 Oct 2017 17:17:35 -0300 Subject: Load is_work through FbData, fixes api key issues after the first login. I have no idea what request after login requires api keys, though. This plugin signs all post-login requests pointlessly. --- facebook/facebook-api.c | 17 +++++++++++++++++ facebook/facebook-data.c | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/facebook/facebook-api.c b/facebook/facebook-api.c index 27a8aab..16bbf6e 100644 --- a/facebook/facebook-api.c +++ b/facebook/facebook-api.c @@ -40,6 +40,7 @@ enum PROP_TOKEN, PROP_UID, PROP_TWEAK, + PROP_WORK, PROP_N }; @@ -155,6 +156,9 @@ fb_api_set_property(GObject *obj, guint prop, const GValue *val, priv->tweak = g_value_get_int(val); fb_http_set_agent(priv->http, fb_api_get_agent_string(priv->tweak, 0)); break; + case PROP_WORK: + priv->is_work = g_value_get_boolean(val); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); @@ -189,6 +193,9 @@ fb_api_get_property(GObject *obj, guint prop, GValue *val, GParamSpec *pspec) case PROP_TWEAK: g_value_set_int(val, priv->tweak); break; + case PROP_WORK: + g_value_set_boolean(val, priv->is_work); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); @@ -321,6 +328,16 @@ fb_api_class_init(FbApiClass *klass) "", 0, G_MAXINT, 0, G_PARAM_READWRITE); + + /** + * FbApi:work: + */ + props[PROP_WORK] = g_param_spec_boolean( + "work", + "Work", + "", + FALSE, + G_PARAM_READWRITE); g_object_class_install_properties(gklass, PROP_N, props); /** diff --git a/facebook/facebook-data.c b/facebook/facebook-data.c index 99cd5e5..608d725 100644 --- a/facebook/facebook-data.c +++ b/facebook/facebook-data.c @@ -169,6 +169,14 @@ fb_data_load(FbData *fata) g_value_unset(&val); } + num = set_getbool(&acct->set, "work"); + if (num != 0) { + g_value_init(&val, G_TYPE_BOOLEAN); + g_value_set_boolean(&val, num); + g_object_set_property(G_OBJECT(priv->api), "work", &val); + g_value_unset(&val); + } + fb_api_rehash(priv->api); return ret; } -- cgit v1.2.3