Changeset 4a5d885
- Timestamp:
- 2011-07-26T11:58:38Z (13 years ago)
- Branches:
- master
- Children:
- 1174899
- Parents:
- 59c9adb4
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
lib/oauth.h
r59c9adb4 r4a5d885 92 92 93 93 /* For reading misc. data. */ 94 void oauth_params_add( GSList **params, const char *key, const char *value ); 95 void oauth_params_free( GSList **params ); 96 char *oauth_params_string( GSList *params ); 94 97 const char *oauth_params_get( GSList **params, const char *key ); -
lib/oauth2.c
r59c9adb4 r4a5d885 23 23 24 24 #include <glib.h> 25 #include "http_client.h" 25 26 #include "oauth2.h" 27 #include "oauth.h" 28 #include "url.h" 26 29 27 30 struct oauth2_service oauth2_service_google = … … 41 44 NULL ); 42 45 } 46 47 struct oauth2_access_token_data 48 { 49 oauth2_token_callback func; 50 gpointer data; 51 }; 52 53 static char *oauth2_json_dumb_get( const char *json, const char *key ); 54 static void oauth2_access_token_done( struct http_request *req ); 55 56 int oauth2_access_token( const struct oauth2_service *sp, 57 const char *auth_type, const char *auth, 58 oauth2_token_callback func, gpointer data ) 59 { 60 GSList *args = NULL; 61 char *args_s, *s; 62 url_t url_p; 63 struct http_request *req; 64 struct oauth2_access_token_data *cb_data; 65 66 if( !url_set( &url_p, sp->base_url ) ) 67 return 0; 68 69 oauth_params_add( &args, "client_id", sp->consumer_key ); 70 oauth_params_add( &args, "client_secret", sp->consumer_secret ); 71 oauth_params_add( &args, "grant_type", auth_type ); 72 if( strcmp( auth_type, OAUTH2_AUTH_CODE ) == 0 ) 73 { 74 oauth_params_add( &args, "redirect_uri", "urn:ietf:wg:oauth:2.0:oob" ); 75 oauth_params_add( &args, "code", auth ); 76 } 77 else 78 { 79 oauth_params_add( &args, "refresh_token", auth ); 80 } 81 args_s = oauth_params_string( args ); 82 oauth_params_free( &args ); 83 84 s = g_strdup_printf( "POST %s%s HTTP/1.0\r\n" 85 "Host: %s\r\n" 86 "Content-Type: application/x-www-form-urlencoded\r\n" 87 "Content-Length: %zd\r\n" 88 "Connection: close\r\n" 89 "\r\n" 90 "%s", url_p.file, "token", url_p.host, strlen( args_s ), args_s ); 91 g_free( args_s ); 92 93 cb_data = g_new0( struct oauth2_access_token_data, 1 ); 94 cb_data->func = func; 95 cb_data->data = data; 96 97 req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS, 98 s, oauth2_access_token_done, cb_data ); 99 100 g_free( s ); 101 102 if( req == NULL ) 103 g_free( cb_data ); 104 105 return req != NULL; 106 } 107 108 static void oauth2_access_token_done( struct http_request *req ) 109 { 110 struct oauth2_access_token_data *cb_data = req->data; 111 char *atoken = NULL, *rtoken = NULL; 112 113 if( req->status_code == 200 ) 114 { 115 atoken = oauth2_json_dumb_get( req->reply_body, "access_token" ); 116 rtoken = oauth2_json_dumb_get( req->reply_body, "refresh_token" ); 117 } 118 cb_data->func( cb_data->data, atoken, rtoken ); 119 g_free( atoken ); 120 g_free( rtoken ); 121 g_free( cb_data ); 122 } 123 124 /* Super dumb. I absolutely refuse to use/add a complete json parser library 125 (adding a new dependency to BitlBee for the first time in.. 6 years?) just 126 to parse 100 bytes of data. So I have to do my own parsing because OAuth2 127 dropped support for XML. (GRRR!) This is very dumb and for example won't 128 work for integer values, nor will it strip/handle backslashes. */ 129 static char *oauth2_json_dumb_get( const char *json, const char *key ) 130 { 131 int is_key; /* 1 == reading key, 0 == reading value */ 132 int found_key = 0; 133 134 while( json && *json ) 135 { 136 /* Grab strings and see if they're what we're looking for. */ 137 if( *json == '"' || *json == '\'' ) 138 { 139 char q = *json; 140 const char *str_start; 141 json ++; 142 str_start = json; 143 144 while( *json ) 145 { 146 /* \' and \" are not string terminators. */ 147 if( *json == '\\' && json[1] == q ) 148 json ++; 149 /* But without a \ it is. */ 150 else if( *json == q ) 151 break; 152 json ++; 153 } 154 if( *json == '\0' ) 155 return NULL; 156 157 if( is_key && strncmp( str_start, key, strlen( key ) ) == 0 ) 158 { 159 found_key = 1; 160 } 161 else if( !is_key && found_key ) 162 { 163 char *ret = g_memdup( str_start, json - str_start + 1 ); 164 ret[json-str_start] = '\0'; 165 return ret; 166 } 167 168 } 169 else if( *json == '{' || *json == ',' ) 170 { 171 found_key = 0; 172 is_key = 1; 173 } 174 else if( *json == ':' ) 175 is_key = 0; 176 177 json ++; 178 } 179 180 return NULL; 181 } -
lib/oauth2.h
r59c9adb4 r4a5d885 2 2 * * 3 3 * BitlBee - An IRC to IM gateway * 4 * Simple OAuth client (consumer) implementation.*4 * Simple OAuth2 client (consumer) implementation. * 5 5 * * 6 6 * Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net> * … … 22 22 \***************************************************************************/ 23 23 24 struct oauth2_info; 24 /* Implementation mostly based on my experience with writing the previous OAuth 25 module, and from http://code.google.com/apis/accounts/docs/OAuth2.html . */ 25 26 26 /* Callback function called twice during the access token request process. 27 Return FALSE if something broke and the process must be aborted. */ 28 typedef gboolean (*oauth_cb)( struct oauth2_info * ); 29 30 struct oauth2_info 31 { 32 const struct oauth_service *sp; 33 34 oauth_cb func; 35 void *data; 36 37 struct http_request *http; 38 39 // char *auth_url; 40 // char *request_token; 41 42 // char *token; 43 // char *token_secret; 44 // GSList *params; 45 }; 27 typedef void (*oauth2_token_callback)( gpointer data, const char *atoken, const char *rtoken ); 46 28 47 29 struct oauth2_service … … 52 34 }; 53 35 36 /* Currently suitable for authenticating to Google Talk only, and only for 37 accounts that have 2-factor authorization enabled. */ 54 38 extern struct oauth2_service oauth2_service_google; 55 39 56 /* http://oauth.net/core/1.0a/#auth_step1 (section 6.1) 57 Request an initial anonymous token which can be used to construct an 58 authorization URL for the user. This is passed to the callback function 59 in a struct oauth2_info. */ 40 #define OAUTH2_AUTH_CODE "authorization_code" 41 #define OAUTH2_AUTH_REFRESH "refresh_token" 42 43 /* Generate a URL the user should open in his/her browser to get an 44 authorization code. */ 60 45 char *oauth2_url( const struct oauth2_service *sp, const char *scope ); 61 46 62 /* http://oauth.net/core/1.0a/#auth_step3 (section 6.3) 63 The user gets a PIN or so which we now exchange for the final access 64 token. This is passed to the callback function in the same 65 struct oauth2_info. */ 66 gboolean oauth2_access_token( const char *pin, struct oauth2_info *st ); 67 68 /* Shouldn't normally be required unless the process is aborted by the user. */ 69 void oauth2_info_free( struct oauth2_info *info ); 47 /* Exchanges an auth code or refresh token for an access token. 48 auth_type is one of the two OAUTH2_AUTH_.. constants above. */ 49 int oauth2_access_token( const struct oauth2_service *sp, 50 const char *auth_type, const char *auth, 51 oauth2_token_callback func, gpointer data ); -
protocols/jabber/jabber.c
r59c9adb4 r4a5d885 98 98 struct im_connection *ic = imcb_new( acc ); 99 99 struct jabber_data *jd = g_new0( struct jabber_data, 1 ); 100 struct ns_srv_reply **srvl = NULL, *srv = NULL; 101 char *connect_to, *s; 102 int i; 100 char *s; 103 101 104 102 /* For now this is needed in the _connected() handlers if using … … 139 137 jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free ); 140 138 jd->buddies = g_hash_table_new( g_str_hash, g_str_equal ); 139 140 if( set_getbool( &acc->set, "oauth" ) ) 141 { 142 /* For the first login with OAuth, we have to authenticate via the browser. 143 For subsequent logins, exchange the refresh token for a valid access 144 token (even though the last one maybe didn't expire yet). */ 145 if( strncmp( acc->pass, "refresh_token=", 14 ) != 0 ) 146 sasl_oauth2_init( ic ); 147 else 148 sasl_oauth2_refresh( ic, acc->pass + 14 ); 149 } 150 else 151 jabber_connect( ic ); 152 } 153 154 /* Separate this from jabber_login() so we can do OAuth first if necessary. 155 Putting this in io.c would probably be more correct. */ 156 void jabber_connect( struct im_connection *ic ) 157 { 158 account_t *acc = ic->acc; 159 struct jabber_data *jd = ic->proto_data; 160 int i; 161 char *connect_to; 162 struct ns_srv_reply **srvl = NULL, *srv = NULL; 141 163 142 164 /* Figure out the hostname to connect to. */ … … 280 302 if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 ) 281 303 return jabber_write( ic, message, strlen( message ) ); 304 305 if( g_strcasecmp( who, "jabber_oauth" ) == 0 ) 306 { 307 if( sasl_oauth2_get_refresh_token( ic, message ) ) 308 { 309 return 1; 310 } 311 else 312 { 313 imcb_error( ic, "OAuth failure" ); 314 imc_logout( ic, TRUE ); 315 } 316 } 282 317 283 318 if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) -
protocols/jabber/jabber.h
r59c9adb4 r4a5d885 92 92 char *username; /* USERNAME@server */ 93 93 char *server; /* username@SERVER -=> server/domain, not hostname */ 94 95 char *oauth2_access_token; 94 96 95 97 /* After changing one of these two (or the priority setting), call … … 232 234 #define XMLNS_IBB "http://jabber.org/protocol/ibb" /* XEP-0047 */ 233 235 236 /* jabber.c */ 237 void jabber_connect( struct im_connection *ic ); 238 234 239 /* iq.c */ 235 240 xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ); … … 316 321 xt_status sasl_pkt_result( struct xt_node *node, gpointer data ); 317 322 gboolean sasl_supported( struct im_connection *ic ); 323 void sasl_oauth2_init( struct im_connection *ic ); 324 int sasl_oauth2_get_refresh_token( struct im_connection *ic, const char *msg ); 325 int sasl_oauth2_refresh( struct im_connection *ic, const char *refresh_token ); 318 326 319 327 /* conference.c */ -
protocols/jabber/sasl.c
r59c9adb4 r4a5d885 76 76 xt_add_attr( reply, "xmlns", XMLNS_SASL ); 77 77 78 if( sup_oauth2 && set_getbool( &ic->acc->set, "oauth" ) ) 79 { 80 imcb_log( ic, "Open this URL in your browser to authenticate: %s", 81 oauth2_url( &oauth2_service_google, 82 "https://www.googleapis.com/auth/googletalk" ) ); 83 xt_free_node( reply ); 84 reply = NULL; 78 if( set_getbool( &ic->acc->set, "oauth" ) ) 79 { 80 int len; 81 82 if( !sup_oauth2 ) 83 { 84 imcb_error( ic, "OAuth requested, but not supported by server" ); 85 imc_logout( ic, FALSE ); 86 xt_free_node( reply ); 87 return XT_ABORT; 88 } 89 90 /* X-OAUTH2 is, not *the* standard OAuth2 SASL/XMPP implementation. 91 It's currently used by GTalk and vaguely documented on 92 http://code.google.com/apis/cloudprint/docs/rawxmpp.html . */ 93 xt_add_attr( reply, "mechanism", "X-OAUTH2" ); 94 95 len = strlen( jd->username ) + strlen( jd->oauth2_access_token ) + 2; 96 s = g_malloc( len + 1 ); 97 s[0] = 0; 98 strcpy( s + 1, jd->username ); 99 strcpy( s + 2 + strlen( jd->username ), jd->oauth2_access_token ); 100 reply->text = base64_encode( (unsigned char *)s, len ); 101 reply->text_len = strlen( reply->text ); 102 g_free( s ); 85 103 } 86 104 else if( sup_digest ) … … 358 376 return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0; 359 377 } 378 379 void sasl_oauth2_init( struct im_connection *ic ) 380 { 381 char *msg, *url; 382 383 imcb_log( ic, "Starting OAuth authentication" ); 384 385 /* Temporary contact, just used to receive the OAuth response. */ 386 imcb_add_buddy( ic, "jabber_oauth", NULL ); 387 url = oauth2_url( &oauth2_service_google, 388 "https://www.googleapis.com/auth/googletalk" ); 389 msg = g_strdup_printf( "Open this URL in your browser to authenticate: %s", url ); 390 imcb_buddy_msg( ic, "jabber_oauth", msg, 0, 0 ); 391 imcb_buddy_msg( ic, "jabber_oauth", "Respond to this message with the returned " 392 "authorization token.", 0, 0 ); 393 394 g_free( msg ); 395 g_free( url ); 396 } 397 398 static gboolean sasl_oauth2_remove_contact( gpointer data, gint fd, b_input_condition cond ) 399 { 400 struct im_connection *ic = data; 401 imcb_remove_buddy( ic, "jabber_oauth", NULL ); 402 return FALSE; 403 } 404 405 static void sasl_oauth2_got_token( gpointer data, const char *access_token, const char *refresh_token ); 406 407 int sasl_oauth2_get_refresh_token( struct im_connection *ic, const char *msg ) 408 { 409 char *code; 410 int ret; 411 412 imcb_log( ic, "Requesting OAuth access token" ); 413 414 /* Don't do it here because the caller may get confused if the contact 415 we're currently sending a message to is deleted. */ 416 b_timeout_add( 1, sasl_oauth2_remove_contact, ic ); 417 418 code = g_strdup( msg ); 419 g_strstrip( code ); 420 ret = oauth2_access_token( &oauth2_service_google, OAUTH2_AUTH_CODE, 421 code, sasl_oauth2_got_token, ic ); 422 423 g_free( code ); 424 return ret; 425 } 426 427 int sasl_oauth2_refresh( struct im_connection *ic, const char *refresh_token ) 428 { 429 return oauth2_access_token( &oauth2_service_google, OAUTH2_AUTH_REFRESH, 430 refresh_token, sasl_oauth2_got_token, ic ); 431 } 432 433 static void sasl_oauth2_got_token( gpointer data, const char *access_token, const char *refresh_token ) 434 { 435 struct im_connection *ic = data; 436 struct jabber_data *jd; 437 438 if( g_slist_find( jabber_connections, ic ) == NULL ) 439 return; 440 441 jd = ic->proto_data; 442 443 if( access_token == NULL ) 444 { 445 imcb_error( ic, "OAuth failure (missing access token)" ); 446 imc_logout( ic, TRUE ); 447 } 448 if( refresh_token != NULL ) 449 { 450 g_free( ic->acc->pass ); 451 ic->acc->pass = g_strdup_printf( "refresh_token=%s", refresh_token ); 452 } 453 454 g_free( jd->oauth2_access_token ); 455 jd->oauth2_access_token = g_strdup( access_token ); 456 457 jabber_connect( ic ); 458 }
Note: See TracChangeset
for help on using the changeset viewer.