Skip to content

Commit

Permalink
Merge pull request #104 from penguin42/loginwork
Browse files Browse the repository at this point in the history
Login updates: Avoid the outdated login API and store access tokens
  • Loading branch information
penguin42 authored Dec 28, 2019
2 parents 9a54ab8 + e1660c4 commit 1d23385
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 34 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down
1 change: 1 addition & 0 deletions libmatrix.c
Original file line number Diff line number Diff line change
Expand Up @@ -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() */
Expand Down
2 changes: 2 additions & 0 deletions libmatrix.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
41 changes: 35 additions & 6 deletions matrix-api.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down
21 changes: 21 additions & 0 deletions matrix-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,27 @@ MatrixApiRequestData *matrix_api_download_thumb(MatrixConnectionData *conn,
MatrixApiBadResponseCallback bad_response_callback,
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
Expand Down
190 changes: 162 additions & 28 deletions matrix-connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

/* libpurple */
#include <debug.h>
#include <libpurple/request.h>
#include <libpurple/core.h>

/* libmatrix */
#include "libmatrix.h"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -223,13 +206,161 @@ 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)
{
PurpleAccount *acct = pc->account;
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);
Expand All @@ -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);
}
}


Expand Down

0 comments on commit 1d23385

Please sign in to comment.