Skip to content
This repository has been archived by the owner on Nov 25, 2022. It is now read-only.

Commit

Permalink
tweak refreshing
Browse files Browse the repository at this point in the history
  • Loading branch information
r10s committed Feb 21, 2019
1 parent 8ab2496 commit 4403d54
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 60 deletions.
2 changes: 1 addition & 1 deletion cmdline/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ int main(int argc, char ** argv)
}
else {
char* oauth2_url = dc_get_oauth2_url(context, addr,
"urn:ietf:wg:oauth:2.0:oob");
"chat.delta:/com.b44t.messenger");
if (oauth2_url==NULL) {
printf("OAuth2 not available for %s.\n", addr);
}
Expand Down
4 changes: 0 additions & 4 deletions cmdline/stress.c
Original file line number Diff line number Diff line change
Expand Up @@ -1059,8 +1059,4 @@ void stress_functions(dc_context_t* context)
assert( res->id != 0 );
dc_lot_unref(res);
}

{
context->cb(context, DC_EVENT_HTTP_POST, (uintptr_t)"https://accounts.google.com/o/oauth2/token?client_id=959970109878-t6pl4k9fmsdvfnobae862urapdmhfvbe.apps.googleusercontent.com&client_secret=g2f_Gc1YUJ-fWjnTkdsuk4Xo&&grant_type=refresh_token&refresh_token=1/RMq1d0QKVF-BN4yJSuBwjzukWTF_puI3IBYtIjtPhi8&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob", 0);
}
}
2 changes: 1 addition & 1 deletion deltachat-core.cbp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<Option object_output="obj/Debug/" />
<Option type="1" />
<Option compiler="gcc" />
<Option parameters="/home/bpetersen/messy/mailboxes/messenger-gmail-susi.db" />
<Option parameters="/home/bpetersen/messy/mailboxes/messenger.db" />
<Compiler>
<Add option="-g" />
</Compiler>
Expand Down
2 changes: 1 addition & 1 deletion docs/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,7 @@ EXCLUDE_PATTERNS =
######################################################
EXCLUDE_SYMBOLS = dc_aheader_t dc_apeerstate_t dc_e2ee_helper_t dc_imap_t dc_job*_t dc_key_t dc_keyring_t dc_loginparam_t dc_mime*_t
EXCLUDE_SYMBOLS += dc_saxparser_t dc_simplify_t dc_smtp_t dc_sqlite3_t dc_strbuilder_t dc_param_t dc_hash_t dc_hashelem_t
EXCLUDE_SYMBOLS += _dc_*
EXCLUDE_SYMBOLS += _dc_* jsmn*
######################################################

# The EXAMPLE_PATH tag can be used to specify one or more files or directories
Expand Down
4 changes: 3 additions & 1 deletion src/dc_configure.c
Original file line number Diff line number Diff line change
Expand Up @@ -743,12 +743,14 @@ void dc_job_do_DC_JOB_CONFIGURE_IMAP(dc_context_t* context, dc_job_t* job)
// the used oauth2 addr may differ, check this.
// if dc_get_oauth2_addr() is not available in the oauth2 implementation,
// just use the given one.
char* oauth2_addr = dc_get_oauth2_addr(context, param->addr);
PROGRESS(10)
char* oauth2_addr = dc_get_oauth2_addr(context, param->mail_pw);
if (oauth2_addr) {
free(param->addr);
param->addr = oauth2_addr;
dc_sqlite3_set_config(context->sql, "addr", param->addr);
}
PROGRESS(20)
}

param_domain = strchr(param->addr, '@');
Expand Down
188 changes: 146 additions & 42 deletions src/dc_oauth2.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
#include "dc_jsmn.h"


// TODO: refactor code so that it works with non-google oauth2 (outlook?, ??)


static int jsoneq(const char *json, jsmntok_t *tok, const char *s) {
// from the jsmn parser example
if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
Expand All @@ -27,43 +30,70 @@ static int is_expired(dc_context_t* context)
"oauth2_timestamp_expires", 0);

if (expire_timestamp<=0) {
dc_log_info(context, 0, "===== OAuth: no expire time =====");
return 0; // timestamp does never expire
}

if (expire_timestamp>time(NULL)) {
dc_log_info(context, 0, "===== OAuth: still valid =====");
return 0; // expire timestamp is in the future and not yet expired
}

dc_log_info(context, 0, "===== OAuth: expired =====");
return 1; // expired
}


/**
* Get url that can be used to initiate an OAuth2 authorisation.
*
* If an OAuth2 authorization is possible for a given e-mail-address,
* this function returns the URL that should be opened in a browser.
*
* If the user authorizes access,
* the given redirect_uri is called by the provider.
* It's up to the UI to handle this call.
*
* The provider will attach some parameters to the url,
* most important the parameter `code` that should be set as the `mail_pw`.
* With `server_flags` set to #DC_LP_AUTH_OAUTH2,
* dc_configure() can be called as usual afterwards.
*
* @memberof dc_context_t
* @param context The context object as created by dc_context_new().
* @param addr E-mail address the user has entered.
* In case the user selects a different e-mail-address during
* authorization, this is corrected in dc_configure()
* @param redirect_uri URL that will get `code` that is used as `mail_pw` then.
* Not all URLs are allowed here, however, the following should work:
* `chat.delta:/PATH`, `http://localhost:PORT/PATH`,
* `https://localhost:PORT/PATH`, `urn:ietf:wg:oauth:2.0:oob`
* (the latter just displays the code the user can copy+paste then)
* @return URL that can be opened in the browser to start OAuth2.
* If OAuth2 is not possible for the given e-mail-address, NULL is returned.
*/
char* dc_get_oauth2_url(dc_context_t* context, const char* addr,
const char* redirect)
const char* redirect_uri)
{
// it's fine to add the "secret" to "offline" apps source code,
// "In this context, the client secret is obviously not treated as a secret."
// https://developers.google.com/identity/protocols/OAuth2
#define CLIENT_SECRET "g2f_Gc1YUJ-fWjnTkdsuk4Xo"
#define CLIENT_ID "959970109878-t6pl4k9fmsdvfnobae862urapdmhfvbe.apps.googleusercontent.com"
#define CLIENT_ID "959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com"
#define AUTH_SCOPE "https%3A%2F%2Fmail.google.com%2F%20email"

char* oauth2_url = NULL;
char* addr_normalized = NULL;
char* redirect_urlencoded = NULL;
char* redirect_uri_urlencoded = NULL;
const char* domain = NULL;

if (context==NULL || context->magic!=DC_CONTEXT_MAGIC
|| redirect_uri==NULL || redirect_uri[0]==0) {
goto cleanup;
}

addr_normalized = dc_addr_normalize(addr);
domain = strchr(addr_normalized, '@');
if (domain==NULL || domain[0]==0) {
goto cleanup;
}
domain++;

redirect_urlencoded = dc_urlencode(redirect);
redirect_uri_urlencoded = dc_urlencode(redirect_uri);
dc_sqlite3_set_config(context->sql, "oauth2_pending_redirect_uri", redirect_uri);

if (strcasecmp(domain, "gmail.com")==0
|| strcasecmp(domain, "googlemail.com")==0) {
Expand All @@ -73,12 +103,12 @@ char* dc_get_oauth2_url(dc_context_t* context, const char* addr,
"&response_type=code"
"&scope=%s"
"&access_type=offline",
CLIENT_ID, redirect_urlencoded, AUTH_SCOPE);
CLIENT_ID, redirect_uri_urlencoded, AUTH_SCOPE);
}

cleanup:
free(addr_normalized);
free(redirect_urlencoded);
free(redirect_uri_urlencoded);
return oauth2_url;
}

Expand All @@ -87,6 +117,10 @@ char* dc_get_oauth2_access_token(dc_context_t* context, const char* code, int fl
{
char* access_token = NULL;
char* refresh_token = NULL;
char* refresh_token_for = NULL;
char* redirect_uri = NULL;
char* redirect_uri_urlencoded = NULL;
int update_redirect_uri_on_success = 0;
char* token_url = NULL;
time_t expires_in = 0;
char* error = NULL;
Expand Down Expand Up @@ -115,30 +149,36 @@ char* dc_get_oauth2_access_token(dc_context_t* context, const char* code, int fl
}

// generate new token: build & call auth url
#define TOKEN_REDIRECT_URI "urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob"

refresh_token = dc_sqlite3_get_config(context->sql, "oauth2_refresh_token", NULL);
if (refresh_token==NULL)
refresh_token_for = dc_sqlite3_get_config(context->sql, "oauth2_refresh_token_for", "unset");
if (refresh_token==NULL || strcmp(refresh_token_for, code)!=0)
{
dc_log_info(context, 0, "===== OAuth: get code =====");
dc_log_info(context, 0, "Generate OAuth2 refresh_token and access_token...");

redirect_uri = dc_sqlite3_get_config(context->sql, "oauth2_pending_redirect_uri", "unset");
redirect_uri_urlencoded = dc_urlencode(redirect_uri);
update_redirect_uri_on_success = 1;

token_url = dc_mprintf("https://accounts.google.com/o/oauth2/token"
"?client_id=%s"
"&client_secret=%s"
"&grant_type=authorization_code"
"&code=%s"
"&redirect_uri=%s",
CLIENT_ID, CLIENT_SECRET, code, TOKEN_REDIRECT_URI);
CLIENT_ID, code, redirect_uri_urlencoded);
}
else
{
dc_log_info(context, 0, "===== OAuth: regen =====");
dc_log_info(context, 0, "Regenerate OAuth2 access_token by refresh_token...");

redirect_uri = dc_sqlite3_get_config(context->sql, "oauth2_redirect_uri", "unset");
redirect_uri_urlencoded = dc_urlencode(redirect_uri);

token_url = dc_mprintf("https://accounts.google.com/o/oauth2/token"
"?client_id=%s"
"&client_secret=%s"
"&grant_type=refresh_token"
"&refresh_token=%s"
"&redirect_uri=%s",
CLIENT_ID, CLIENT_SECRET, refresh_token, TOKEN_REDIRECT_URI);
CLIENT_ID, refresh_token, redirect_uri_urlencoded);
}

json = (char*)context->cb(context, DC_EVENT_HTTP_POST, (uintptr_t)token_url, 0);
Expand All @@ -156,10 +196,10 @@ char* dc_get_oauth2_access_token(dc_context_t* context, const char* code, int fl
}

for (int i = 1; i < tok_cnt; i++) {
if (jsoneq(json, &tok[i], "access_token")==0) {
if (access_token==NULL && jsoneq(json, &tok[i], "access_token")==0) {
access_token = jsondup(json, &tok[i+1]);
}
else if (jsoneq(json, &tok[i], "refresh_token")==0) {
else if (refresh_token==NULL && jsoneq(json, &tok[i], "refresh_token")==0) {
refresh_token = jsondup(json, &tok[i+1]);
}
else if (jsoneq(json, &tok[i], "expires_in")==0) {
Expand All @@ -175,10 +215,10 @@ char* dc_get_oauth2_access_token(dc_context_t* context, const char* code, int fl
free(expires_in_str);
}
}
else if (jsoneq(json, &tok[i], "error")==0) {
else if (error==NULL && jsoneq(json, &tok[i], "error")==0) {
error = jsondup(json, &tok[i+1]);
}
else if (jsoneq(json, &tok[i], "error_description")==0) {
else if (error_description==NULL && jsoneq(json, &tok[i], "error_description")==0) {
error_description = jsondup(json, &tok[i+1]);
}
}
Expand All @@ -190,26 +230,33 @@ char* dc_get_oauth2_access_token(dc_context_t* context, const char* code, int fl
// continue, errors do not imply everything went wrong
}

// update refresh_token if given, typically on the first round, but we update it later as well.
if (refresh_token && refresh_token[0]) {
dc_sqlite3_set_config(context->sql, "oauth2_refresh_token", refresh_token);
dc_sqlite3_set_config(context->sql, "oauth2_refresh_token_for", code);
}

// after that, save the access token.
// if it's unset, we may get it in the next round as we have the refresh_token now.
if (access_token==NULL || access_token[0]==0) {
dc_log_warning(context, 0, "Failed to find OAuth2 access token");
goto cleanup;
}

dc_sqlite3_set_config(context->sql, "oauth2_access_token", access_token);

dc_sqlite3_set_config_int64(context->sql, "oauth2_timestamp_expires",
expires_in? time(NULL)+expires_in-5/*refresh a bet before*/ : 0);

// update refresh_token if given,
// typically this is on the first round with `grant_type=authorization_code`
// but we update it later, too.
if (refresh_token && refresh_token[0]) {
dc_sqlite3_set_config(context->sql, "oauth2_refresh_token", refresh_token);
if (update_redirect_uri_on_success) {
dc_sqlite3_set_config(context->sql, "oauth2_redirect_uri", redirect_uri);
}

cleanup:
if (locked) { pthread_mutex_unlock(&context->oauth2_critical); }
free(refresh_token);
free(refresh_token_for);
free(redirect_uri);
free(redirect_uri_urlencoded);
free(token_url);
free(json);
free(error);
Expand All @@ -218,22 +265,79 @@ char* dc_get_oauth2_access_token(dc_context_t* context, const char* code, int fl
}


char* dc_get_oauth2_addr(dc_context_t* context, const char* addr)
static char* get_oauth2_addr(dc_context_t* context, char* access_token)
{
// TODO:
//
// calling https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=TOKEN
char* addr_out = NULL;
char* userinfo_url = NULL;
char* json = NULL;
jsmn_parser parser;
jsmntok_t tok[128]; // we do not expect nor read more tokens
int tok_cnt = 0;

if (context==NULL || context->magic!=DC_CONTEXT_MAGIC
|| access_token==NULL || access_token[0]==0) {
goto cleanup;
}

userinfo_url = dc_mprintf(
"https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=%s",
access_token);
// returns sth. as
// {
// "id": "100000000831024152393",
// "email": "[email protected]",
// "verified_email": true,
// "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg"
// }
// the returned email-addr must be used
// instead of the one initially entered by the user as he may have
// selected a different account to use in the browser.
//
// if the address cannot be find out this way, return NULL and the caller will use the given one.
return NULL;

json = (char*)context->cb(context, DC_EVENT_HTTP_GET, (uintptr_t)userinfo_url, 0);
if (json==NULL) {
dc_log_warning(context, 0, "Error getting userinfo.");
goto cleanup;
}

jsmn_init(&parser);
tok_cnt = jsmn_parse(&parser, json, strlen(json), tok, sizeof(tok)/sizeof(tok[0]));
if (tok_cnt<2 || tok[0].type!=JSMN_OBJECT) {
dc_log_warning(context, 0, "Failed to parse userinfo.");
goto cleanup;
}

for (int i = 1; i < tok_cnt; i++) {
if (addr_out==NULL && jsoneq(json, &tok[i], "email")==0) {
addr_out = jsondup(json, &tok[i+1]);
}
}

if (addr_out==NULL) {
dc_log_warning(context, 0, "E-mail missing in userinfo.");
}

cleanup:
free(userinfo_url);
free(json);
return addr_out;
}


char* dc_get_oauth2_addr(dc_context_t* context, const char* code)
{
char* access_token = NULL;
char* addr_out = NULL;

if (context==NULL || context->magic!=DC_CONTEXT_MAGIC) {
goto cleanup;
}

access_token = dc_get_oauth2_access_token(context, code, 0);
addr_out = get_oauth2_addr(context, access_token);
if (addr_out==NULL) {
free(access_token);
access_token = dc_get_oauth2_access_token(context, code, DC_REGENERATE);
addr_out = get_oauth2_addr(context, access_token);
}

cleanup:
free(access_token);
return addr_out;
}
5 changes: 4 additions & 1 deletion src/dc_oauth2.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ extern "C" {

#define DC_REGENERATE 0x01


// the following function may block due http-requests;
// must not be called from the main thread or by the ui!
char* dc_get_oauth2_access_token(dc_context_t*, const char* code, int flags);
char* dc_get_oauth2_addr (dc_context_t*, const char* addr);
char* dc_get_oauth2_addr (dc_context_t*, const char* code);


#ifdef __cplusplus
Expand Down
Loading

0 comments on commit 4403d54

Please sign in to comment.