source: lib/oauth2.c

Last change on this file was 82149f4, checked in by GitHub <noreply@…>, at 2023-03-04T19:59:09Z

Fixes related to external-json-parser (#165)

  • configure: append conditionally json-parser to pc file

json-parser needs to be appended to Requires section of pkg-config file
when bitlbee is configured to use external-json-parser. This change is
needed for external plugins like bitlbee-steam.

  • Makefile: do not install json.h when system json-parser is used

install-dev target should not install bundled json.h when bitlbee is
configured to use external-json-parser.

  • Use correct json.h header file with respect to external_json_parser value

The preprocessor must include correct json.h header file with respect to
external_json_parser value, otherwise function prototypes and other
definitions do not need to correspond with object used for linking.

The state before this commit is that local version lib/json.h is used
always for compilation and external_json_parser variable controls if
local lib/json.o or global libjsonparser.so will be linked.

In order to fix this problem, #include directives in lib/json_util.h and
lib/oauth2.c were changed from "json.h" to <json.h> and preprocessor -I
flags were moved after conditional json-parser flags, which is enough
for solving the issue. Additionally, USE_EXTERNAL_JSON_PARSER macro is
exported when external-json-parser is used and it is used in lib/json.h
to trigger an error message, which should prevent similar mistakes in
future.

  • Property mode set to 100644
File size: 6.8 KB
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Simple OAuth2 client (consumer) implementation.                          *
5*                                                                           *
6*  Copyright 2010-2013 Wilmer van der Gaast <wilmer@gaast.net>              *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24/* Out of protest, I should rename this file. OAuth2 is a pathetic joke, and
25   of all things, DEFINITELY NOT A STANDARD. The only thing various OAuth2
26   implementations have in common is that name, wrongfully stolen from
27   a pretty nice standard called OAuth 1.0a. That, and the fact that they
28   use JSON. Wait, no, Facebook's version doesn't use JSON. For some of its
29   responses.
30
31   Apparently too many people were too retarded to comprehend the elementary
32   bits of crypto in OAuth 1.0a (took me one afternoon to implement) so
33   the standard was replaced with what comes down to a complicated scheme
34   around what's really just application-specific passwords.
35
36   And then a bunch of mostly incompatible implementations. Great work, guys.
37
38   http://hueniverse.com/2012/07/oauth-2-0-and-the-road-to-hell/ */
39
40#include <glib.h>
41#include "http_client.h"
42#include "oauth2.h"
43#include "oauth.h"
44#include <json.h>
45#include "json_util.h"
46#include "url.h"
47
48char *oauth2_url(const struct oauth2_service *sp)
49{
50        return g_strconcat(sp->auth_url,
51                           "?scope=", sp->scope,
52                           "&response_type=code"
53                           "&redirect_uri=", sp->redirect_url,
54                           "&client_id=", sp->consumer_key,
55                           NULL);
56}
57
58struct oauth2_access_token_data {
59        oauth2_token_callback func;
60        gpointer data;
61};
62
63static void oauth2_access_token_done(struct http_request *req);
64
65int oauth2_access_token(const struct oauth2_service *sp,
66                        const char *auth_type, const char *auth,
67                        oauth2_token_callback func, gpointer data)
68{
69        GSList *args = NULL;
70        char *args_s, *s;
71        url_t url_p;
72        struct http_request *req;
73        struct oauth2_access_token_data *cb_data;
74
75        if (!url_set(&url_p, sp->token_url)) {
76                return 0;
77        }
78
79        oauth_params_add(&args, "client_id", sp->consumer_key);
80        oauth_params_add(&args, "client_secret", sp->consumer_secret);
81        oauth_params_add(&args, "grant_type", auth_type);
82        if (strcmp(auth_type, OAUTH2_AUTH_CODE) == 0) {
83                oauth_params_add(&args, "redirect_uri", sp->redirect_url);
84                oauth_params_add(&args, "code", auth);
85        } else {
86                oauth_params_add(&args, "refresh_token", auth);
87        }
88        args_s = oauth_params_string(args);
89        oauth_params_free(&args);
90
91        s = g_strdup_printf("POST %s HTTP/1.0\r\n"
92                            "Host: %s\r\n"
93                            "Content-Type: application/x-www-form-urlencoded\r\n"
94                            "Content-Length: %zd\r\n"
95                            "\r\n"
96                            "%s", url_p.file, url_p.host, strlen(args_s), args_s);
97        g_free(args_s);
98
99        cb_data = g_new0(struct oauth2_access_token_data, 1);
100        cb_data->func = func;
101        cb_data->data = data;
102
103        req = http_dorequest(url_p.host, url_p.port, url_p.proto == PROTO_HTTPS,
104                             s, oauth2_access_token_done, cb_data);
105
106        g_free(s);
107
108        if (req == NULL) {
109                g_free(cb_data);
110        }
111
112        return req != NULL;
113}
114
115static char* oauth2_parse_error(json_value *e)
116{
117        /* This does a reasonable job with some of the flavours of error
118           responses I've seen. Because apparently it's not standardised. */
119
120        if (e->type == json_object) {
121                /* Facebook style */
122                const char *msg = json_o_str(e, "message");
123                const char *type = json_o_str(e, "type");
124                json_value *code_o = json_o_get(e, "code");
125                int code = 0;
126
127                if (code_o && code_o->type == json_integer) {
128                        code = code_o->u.integer;
129                }
130
131                return g_strdup_printf("Error %d: %s", code, msg ? msg : type ? type : "Unknown error");
132        } else if (e->type == json_string) {
133                return g_strdup(e->u.string.ptr);
134        }
135        return NULL;
136}
137
138static void oauth2_access_token_done(struct http_request *req)
139{
140        struct oauth2_access_token_data *cb_data = req->data;
141        char *atoken = NULL, *rtoken = NULL, *error = NULL;
142        char *content_type = NULL;
143
144        if (req->status_code <= 0 && !req->reply_body) {
145                cb_data->func(cb_data->data, NULL, NULL, req->status_string);
146                g_free(cb_data);
147                return;
148        }
149
150        if (getenv("BITLBEE_DEBUG")) {
151                printf("%s\n", req->reply_body);
152        }
153
154        content_type = get_rfc822_header(req->reply_headers, "Content-Type", 0);
155
156        if (content_type && (strstr(content_type, "application/json") ||
157                             strstr(content_type, "text/javascript"))) {
158                json_value *js = json_parse(req->reply_body, req->body_size);
159                if (js && js->type == json_object) {
160                        JSON_O_FOREACH(js, k, v){
161                                if (strcmp(k, "error") == 0) {
162                                        error = oauth2_parse_error(v);
163                                }
164                                if (v->type != json_string) {
165                                        continue;
166                                }
167                                if (strcmp(k, "access_token") == 0) {
168                                        atoken = g_strdup(v->u.string.ptr);
169                                }
170                                if (strcmp(k, "refresh_token") == 0) {
171                                        rtoken = g_strdup(v->u.string.ptr);
172                                }
173                        }
174                }
175                json_value_free(js);
176        } else {
177                /* Facebook use their own odd format here, seems to be URL-encoded. */
178                GSList *p_in = NULL;
179
180                oauth_params_parse(&p_in, req->reply_body);
181                atoken = g_strdup(oauth_params_get(&p_in, "access_token"));
182                rtoken = g_strdup(oauth_params_get(&p_in, "refresh_token"));
183                oauth_params_free(&p_in);
184        }
185        if (getenv("BITLBEE_DEBUG")) {
186                printf("Extracted atoken=%s rtoken=%s\n", atoken, rtoken);
187        }
188        if (!atoken && !rtoken && !error) {
189                error = g_strdup("Unusable response");
190        }
191
192        cb_data->func(cb_data->data, atoken, rtoken, error);
193        g_free(content_type);
194        g_free(atoken);
195        g_free(rtoken);
196        g_free(error);
197        g_free(cb_data);
198}
Note: See TracBrowser for help on using the repository browser.