diff options
author | Dave Gilbert <dave@treblig.org> | 2019-12-28 19:53:50 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-12-28 19:53:50 +0000 |
commit | 1d23385e6c22f63591fcbfc85c09999953c388ed (patch) | |
tree | a7614625c0bf6046016a6a48aef5d75f1e2873dc | |
parent | 9a54ab8cb803a873f0168ffd5825ed6885f0b5a2 (diff) | |
parent | e1660c4e2e7576c4ce30aa46075c63256d3ccc07 (diff) | |
download | purple-matrix-1d23385e6c22f63591fcbfc85c09999953c388ed.tar.gz |
Merge pull request #104 from penguin42/loginwork
Login updates: Avoid the outdated login API and store access tokens
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | libmatrix.c | 1 | ||||
-rw-r--r-- | libmatrix.h | 2 | ||||
-rw-r--r-- | matrix-api.c | 41 | ||||
-rw-r--r-- | matrix-api.h | 21 | ||||
-rw-r--r-- | matrix-connection.c | 190 |
6 files changed, 224 insertions, 34 deletions
@@ -86,6 +86,9 @@ You will then need to restart Pidgin, after which you should be able to add a from the 'Protocol' dropdown. * Enter your matrix ID on the homeserver (e.g. '@bob:matrix.org' or 'bob') as the 'username', and the password in the 'password' field. + * If you don't enter your password, you'll be prompted for it when you try + to connect; this won't get saved unless you click 'save password' but an + access token is stored instead. * On the 'Advanced' tab, enter the URL of your homeserver. diff --git a/libmatrix.c b/libmatrix.c index 5b2d4ae..4783056 100644 --- a/libmatrix.c +++ b/libmatrix.c @@ -272,6 +272,7 @@ static char *matrixprpl_get_cb_real_name(PurpleConnection *gc, int id, static PurplePluginProtocolInfo prpl_info = { OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_CHAT_TOPIC | + OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_IM_IMAGE, /* options */ NULL, /* user_splits, initialized in matrixprpl_init() */ NULL, /* protocol_options, initialized in matrixprpl_init() */ diff --git a/libmatrix.h b/libmatrix.h index 86ce891..852d5f8 100644 --- a/libmatrix.h +++ b/libmatrix.h @@ -117,6 +117,8 @@ #define PRPL_ACCOUNT_OPT_SKIP_OLD_MESSAGES "skip_old_messages" /* Pickled account info from olm_pickle_account */ #define PRPL_ACCOUNT_OPT_OLM_ACCOUNT_KEYS "olm_account_keys" +/* Access token, after a login */ +#define PRPL_ACCOUNT_OPT_ACCESS_TOKEN "access_token" /* defaults for account options */ #define DEFAULT_HOME_SERVER "https://matrix.org" diff --git a/matrix-api.c b/matrix-api.c index 7d1b6f1..2eae2a9 100644 --- a/matrix-api.c +++ b/matrix-api.c @@ -601,14 +601,20 @@ void matrix_api_cancel(MatrixApiRequestData *data) gchar *_build_login_body(const gchar *username, const gchar *password, const gchar *device_id) { - JsonObject *body; + JsonObject *body, *ident; JsonNode *node; JsonGenerator *generator; gchar *result; body = json_object_new(); json_object_set_string_member(body, "type", "m.login.password"); - json_object_set_string_member(body, "user", username); + + ident = json_object_new(); + /* TODO: Support 3pid rather than username */ + json_object_set_string_member(ident, "type", "m.id.user"); + json_object_set_string_member(ident, "user", username); + json_object_set_object_member(body, "identifier", ident); + json_object_set_string_member(body, "password", password); json_object_set_string_member(body, "initial_device_display_name", "purple-matrix"); if (device_id != NULL) @@ -638,9 +644,7 @@ MatrixApiRequestData *matrix_api_password_login(MatrixConnectionData *conn, purple_debug_info("matrixprpl", "logging in %s\n", username); - // As per https://github.com/matrix-org/synapse/pull/459, synapse - // didn't expose login at 'r0'. - url = g_strconcat(conn->homeserver, "_matrix/client/api/v1/login", + url = g_strconcat(conn->homeserver, "_matrix/client/r0/login", NULL); json = _build_login_body(username, password, device_id); @@ -1007,6 +1011,31 @@ MatrixApiRequestData *matrix_api_download_thumb(MatrixConnectionData *conn, return fetch_data; } +/** + * Returns the userid for our access token, mostly as a check our token + * is valid. + */ +MatrixApiRequestData *matrix_api_whoami(MatrixConnectionData *conn, + MatrixApiCallback callback, + MatrixApiErrorCallback error_callback, + MatrixApiBadResponseCallback bad_response_callback, + gpointer user_data) +{ + GString *url; + MatrixApiRequestData *fetch_data; + + url = g_string_new(conn->homeserver); + g_string_append_printf(url, + "_matrix/client/r0/account/whoami?access_token=%s", + purple_url_encode(conn->access_token)); + + fetch_data = matrix_api_start(url->str, "GET", NULL, conn, callback, + error_callback, bad_response_callback, user_data, 10*1024); + g_string_free(url, TRUE); + + return fetch_data; +} + MatrixApiRequestData *matrix_api_upload_keys(MatrixConnectionData *conn, JsonObject *device_keys, JsonObject *one_time_keys, MatrixApiCallback callback, @@ -1045,7 +1074,7 @@ MatrixApiRequestData *matrix_api_upload_keys(MatrixConnectionData *conn, fetch_data = matrix_api_start_full(url->str, "POST", "Content-Type: application/json", json, NULL, 0, conn, callback, error_callback, bad_response_callback, - user_data, 1024); + user_data, 10*1024); g_free(json); g_string_free(url, TRUE); diff --git a/matrix-api.h b/matrix-api.h index 763f587..6a22a40 100644 --- a/matrix-api.h +++ b/matrix-api.h @@ -363,6 +363,27 @@ MatrixApiRequestData *matrix_api_download_thumb(MatrixConnectionData *conn, gpointer user_data); /** + * Returns the userid for our access token, mostly as a check our token + * is valid. + * + * @param conn The connection with which to make the request + * @param callback Function to be called when the request completes + * @param error_callback Function to be called if there is an error making + * the request. If NULL, matrix_api_error will be + * used. + * @param bad_response_callback Function to be called if the API gives a non-200 + * response. If NULL, matrix_api_bad_response will be + * used. + * @param user_data Opaque data to be passed to the callbacks + * + */ +MatrixApiRequestData *matrix_api_whoami(MatrixConnectionData *conn, + MatrixApiCallback callback, + MatrixApiErrorCallback error_callback, + MatrixApiBadResponseCallback bad_response_callback, + gpointer user_data); + +/** * e2e: Upload keys; one or more of the device keys and the one time keys * @param conn The connection with which to make the request * @param device_keys (optional) Json Object with the signed device keys diff --git a/matrix-connection.c b/matrix-connection.c index 736782b..1b071db 100644 --- a/matrix-connection.c +++ b/matrix-connection.c @@ -26,6 +26,8 @@ /* libpurple */ #include <debug.h> +#include <libpurple/request.h> +#include <libpurple/core.h> /* libmatrix */ #include "libmatrix.h" @@ -159,37 +161,18 @@ static gboolean _account_has_active_conversations(PurpleAccount *account) return FALSE; } - -static void _login_completed(MatrixConnectionData *conn, - gpointer user_data, - JsonNode *json_root, - const char *raw_body, size_t raw_body_len, const char *content_type) +static void _start_sync(MatrixConnectionData *conn) { PurpleConnection *pc = conn->pc; - JsonObject *root_obj; - const gchar *access_token; - const gchar *next_batch; - const gchar *device_id; gboolean needs_full_state_sync = TRUE; - - root_obj = matrix_json_node_get_object(json_root); - access_token = matrix_json_object_get_string_member(root_obj, - "access_token"); - if(access_token == NULL) { - purple_connection_error_reason(pc, - PURPLE_CONNECTION_ERROR_OTHER_ERROR, - "No access_token in /login response"); - return; - } - conn->access_token = g_strdup(access_token); - conn->user_id = g_strdup(matrix_json_object_get_string_member(root_obj, - "user_id")); - device_id = matrix_json_object_get_string_member(root_obj, "device_id"); - purple_account_set_string(pc->account, "device_id", device_id); + const gchar *next_batch; + const gchar *device_id = purple_account_get_string(pc->account, + "device_id", NULL); if (device_id) { matrix_e2e_get_device_keys(conn, device_id); } + /* start the sync loop */ next_batch = purple_account_get_string(pc->account, PRPL_ACCOUNT_OPT_NEXT_BATCH, NULL); @@ -223,6 +206,152 @@ static void _login_completed(MatrixConnectionData *conn, _start_next_sync(conn, next_batch, needs_full_state_sync); } +static void _login_completed(MatrixConnectionData *conn, + gpointer user_data, + JsonNode *json_root, + const char *raw_body, size_t raw_body_len, const char *content_type) +{ + PurpleConnection *pc = conn->pc; + JsonObject *root_obj; + const gchar *access_token; + const gchar *device_id; + + root_obj = matrix_json_node_get_object(json_root); + access_token = matrix_json_object_get_string_member(root_obj, + "access_token"); + if(access_token == NULL) { + purple_connection_error_reason(pc, + PURPLE_CONNECTION_ERROR_OTHER_ERROR, + "No access_token in /login response"); + return; + } + conn->access_token = g_strdup(access_token); + conn->user_id = g_strdup(matrix_json_object_get_string_member(root_obj, + "user_id")); + device_id = matrix_json_object_get_string_member(root_obj, "device_id"); + purple_account_set_string(pc->account, "device_id", device_id); + purple_account_set_string(pc->account, PRPL_ACCOUNT_OPT_ACCESS_TOKEN, + access_token); + + _start_sync(conn); +} + +/* + * Callback from _password_login when the user enters a password. + */ +static void +_password_received(PurpleConnection *gc, PurpleRequestFields *fields) +{ + PurpleAccount *acct; + const char *entry; + MatrixConnectionData *conn; + gboolean remember; + + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(gc)) + return; + + acct = purple_connection_get_account(gc); + conn = purple_connection_get_protocol_data(gc); + + entry = purple_request_fields_get_string(fields, "password"); + remember = purple_request_fields_get_bool(fields, "remember"); + + if (!entry || !*entry) + { + purple_notify_error(acct, NULL, _("Password is required to sign on."), NULL); + return; + } + + if (remember) + purple_account_set_remember_password(acct, TRUE); + + purple_account_set_password(acct, entry); + + matrix_api_password_login(conn, acct->username, + entry, + purple_account_get_string(acct, "device_id", NULL), + _login_completed, conn); +} + + +static void +_password_cancel(PurpleConnection *gc, PurpleRequestFields *fields) +{ + PurpleAccount *account; + + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(gc)) + return; + + account = purple_connection_get_account(gc); + + /* Disable the account as the user has cancelled connecting */ + purple_account_set_enabled(account, purple_core_get_ui(), FALSE); +} + +/* + * Start a passworded login. + */ +static void _password_login(MatrixConnectionData *conn, PurpleAccount *acct) +{ + const char *password = purple_account_get_password(acct); + + if (password) { + matrix_api_password_login(conn, acct->username, + password, + purple_account_get_string(acct, "device_id", NULL), + _login_completed, conn); + } else { + purple_account_request_password(acct,G_CALLBACK( _password_received), + G_CALLBACK(_password_cancel), conn->pc); + } +} + + +/* + * If we get an error during whoami just fall back to password + * login. + */ +static void _whoami_error(MatrixConnectionData *conn, + gpointer user_data, const gchar *error_message) +{ + PurpleAccount *acct = user_data; + purple_debug_info("matrixprpl", "_whoami_error: %s\n", error_message); + _password_login(conn, acct); +} + +/* + * If we get a bad response just fall back to password login + */ +static void _whoami_badresp(MatrixConnectionData *conn, gpointer user_data, + int http_response_code, struct _JsonNode *json_root) +{ + purple_debug_info("matrixprpl", "_whoami_badresp\n"); + _whoami_error(conn, user_data, "Bad response"); +} + +/* + * A response from the whoami we issued to validate our access token + * If it's succesful then we can start the connection. + */ +static void _whoami_completed(MatrixConnectionData *conn, + gpointer user_data, + JsonNode *json_root, + const char *raw_body, size_t raw_body_len, const char *content_type) +{ + JsonObject *root_obj = matrix_json_node_get_object(json_root); + const gchar *user_id = matrix_json_object_get_string_member(root_obj, + "user_id"); + + purple_debug_info("matrixprpl", "_whoami_completed got %s\n", user_id); + if (!user_id) { + return _whoami_error(conn, user_data, "no user_id"); + } + // TODO: That is out user_id - right? + conn->user_id = g_strdup(user_id); + _start_sync(conn); +} void matrix_connection_start_login(PurpleConnection *pc) { @@ -230,6 +359,8 @@ void matrix_connection_start_login(PurpleConnection *pc) MatrixConnectionData *conn = purple_connection_get_protocol_data(pc); const gchar *homeserver = purple_account_get_string(pc->account, PRPL_ACCOUNT_OPT_HOME_SERVER, DEFAULT_HOME_SERVER); + const gchar *access_token = purple_account_get_string(pc->account, + PRPL_ACCOUNT_OPT_ACCESS_TOKEN, NULL); if(!g_str_has_suffix(homeserver, "/")) { conn->homeserver = g_strconcat(homeserver, "/", NULL); @@ -240,10 +371,13 @@ void matrix_connection_start_login(PurpleConnection *pc) purple_connection_set_state(pc, PURPLE_CONNECTING); purple_connection_update_progress(pc, _("Logging in"), 0, 3); - matrix_api_password_login(conn, acct->username, - purple_account_get_password(acct), - purple_account_get_string(acct, "device_id", NULL), - _login_completed, conn); + if (access_token) { + conn->access_token = g_strdup(access_token); + matrix_api_whoami(conn, _whoami_completed, _whoami_error, + _whoami_badresp, conn); + } else { + _password_login(conn, acct); + } } |