aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Gilbert <dave@treblig.org>2019-12-28 19:53:50 +0000
committerGitHub <noreply@github.com>2019-12-28 19:53:50 +0000
commit1d23385e6c22f63591fcbfc85c09999953c388ed (patch)
treea7614625c0bf6046016a6a48aef5d75f1e2873dc
parent9a54ab8cb803a873f0168ffd5825ed6885f0b5a2 (diff)
parente1660c4e2e7576c4ce30aa46075c63256d3ccc07 (diff)
downloadpurple-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.md3
-rw-r--r--libmatrix.c1
-rw-r--r--libmatrix.h2
-rw-r--r--matrix-api.c41
-rw-r--r--matrix-api.h21
-rw-r--r--matrix-connection.c190
6 files changed, 224 insertions, 34 deletions
diff --git a/README.md b/README.md
index 0f7461c..710fc4b 100644
--- a/README.md
+++ b/README.md
@@ -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);
+ }
}