Changeset 4a5d885


Ignore:
Timestamp:
2011-07-26T11:58:38Z (13 years ago)
Author:
Wilmer van der Gaast <wilmer@…>
Branches:
master
Children:
1174899
Parents:
59c9adb4
Message:

Working OAuth2 support. Needs some more debugging (error handling is not
great and imc_logout() gets (rightfully) confused when jabber_data is empty).

Files:
6 edited

Legend:

Unmodified
Added
Removed
  • lib/oauth.h

    r59c9adb4 r4a5d885  
    9292
    9393/* For reading misc. data. */
     94void oauth_params_add( GSList **params, const char *key, const char *value );
     95void oauth_params_free( GSList **params );
     96char *oauth_params_string( GSList *params );
    9497const char *oauth_params_get( GSList **params, const char *key );
  • lib/oauth2.c

    r59c9adb4 r4a5d885  
    2323
    2424#include <glib.h>
     25#include "http_client.h"
    2526#include "oauth2.h"
     27#include "oauth.h"
     28#include "url.h"
    2629
    2730struct oauth2_service oauth2_service_google =
     
    4144                            NULL );
    4245}
     46
     47struct oauth2_access_token_data
     48{
     49        oauth2_token_callback func;
     50        gpointer data;
     51};
     52
     53static char *oauth2_json_dumb_get( const char *json, const char *key );
     54static void oauth2_access_token_done( struct http_request *req );
     55
     56int 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
     108static 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. */
     129static 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  
    22*                                                                           *
    33*  BitlBee - An IRC to IM gateway                                           *
    4 *  Simple OAuth client (consumer) implementation.                           *
     4*  Simple OAuth2 client (consumer) implementation.                          *
    55*                                                                           *
    66*  Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net>              *
     
    2222\***************************************************************************/
    2323
    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 . */
    2526
    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 };
     27typedef void (*oauth2_token_callback)( gpointer data, const char *atoken, const char *rtoken );
    4628
    4729struct oauth2_service
     
    5234};
    5335
     36/* Currently suitable for authenticating to Google Talk only, and only for
     37   accounts that have 2-factor authorization enabled. */
    5438extern struct oauth2_service oauth2_service_google;
    5539
    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. */
    6045char *oauth2_url( const struct oauth2_service *sp, const char *scope );
    6146
    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. */
     49int 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  
    9898        struct im_connection *ic = imcb_new( acc );
    9999        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;
    103101       
    104102        /* For now this is needed in the _connected() handlers if using
     
    139137        jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free );
    140138        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. */
     156void 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;
    141163       
    142164        /* Figure out the hostname to connect to. */
     
    280302        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
    281303                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        }
    282317       
    283318        if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
  • protocols/jabber/jabber.h

    r59c9adb4 r4a5d885  
    9292        char *username;         /* USERNAME@server */
    9393        char *server;           /* username@SERVER -=> server/domain, not hostname */
     94       
     95        char *oauth2_access_token;
    9496       
    9597        /* After changing one of these two (or the priority setting), call
     
    232234#define XMLNS_IBB          "http://jabber.org/protocol/ibb"                      /* XEP-0047 */
    233235
     236/* jabber.c */
     237void jabber_connect( struct im_connection *ic );
     238
    234239/* iq.c */
    235240xt_status jabber_pkt_iq( struct xt_node *node, gpointer data );
     
    316321xt_status sasl_pkt_result( struct xt_node *node, gpointer data );
    317322gboolean sasl_supported( struct im_connection *ic );
     323void sasl_oauth2_init( struct im_connection *ic );
     324int sasl_oauth2_get_refresh_token( struct im_connection *ic, const char *msg );
     325int sasl_oauth2_refresh( struct im_connection *ic, const char *refresh_token );
    318326
    319327/* conference.c */
  • protocols/jabber/sasl.c

    r59c9adb4 r4a5d885  
    7676        xt_add_attr( reply, "xmlns", XMLNS_SASL );
    7777       
    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 );
    85103        }
    86104        else if( sup_digest )
     
    358376        return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0;
    359377}
     378
     379void 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
     398static 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
     405static void sasl_oauth2_got_token( gpointer data, const char *access_token, const char *refresh_token );
     406
     407int 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
     427int 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
     433static 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.