summaryrefslogtreecommitdiffabout
path: root/lib/oauth-consumer.cc
blob: 0c4c9e38eb1d3d3c5710be075bb30a762bff1c65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <opkele/oauth/consumer.h>
#include <opkele/exception.h>
#include <opkele/util.h>
#include <opkele/curl.h>
#include <opkele/debug.h>

#include "config.h"
#ifdef HAVE_LIBUUID
# include <uuid/uuid.h>
#endif

namespace opkele {
    namespace oauth {

	const service_endpoint_t&
	    simple_provider_endpoints::get_request_token_endpoint() const {
	    return sep_request_token; }
	const service_endpoint_t&
	    simple_provider_endpoints::get_authorize_user_endpoint() const {
	    return sep_authorize_user; }
	const service_endpoint_t&
	    simple_provider_endpoints::get_access_token_endpoint() const {
	    return sep_access_token; }
	service_endpoint_t&
	    simple_provider_endpoints::get_url_endpoint(service_endpoint_t& sep,
		    const string& url) const {
	    sep = sep_generic;
	    sep.url = url;
	    return sep; }

	token_t basic_consumer::get_request_token() {
	    return acquire_token(get_endpoints().get_request_token_endpoint());
	}

	const string basic_consumer::get_authorize_url(const token_t& rt,const string& callback) {
	    fields_t f;
	    f.set_field("oauth_token",rt.key);
	    if(!callback.empty())
		f.set_field("oauth_callback",callback);
	    return f.append_query(
		    get_endpoints().get_authorize_user_endpoint().url );
	}

	token_t basic_consumer::get_access_token(const token_t& rt) {
	    return acquire_token(get_endpoints().get_access_token_endpoint(),&rt);
	}

	const string basic_consumer::signature(
		const string& method, const string& url,
		const basic_fields& fields,
		const token_t* at) {
	    if(fields.get_field("oauth_signature_method")!="HMAC-SHA1")
		throw opkele::not_implemented(OPKELE_CP_
			"only HMAC-SHA1 signature is implemented");
	    string key = util::url_encode(consumer_token.secret);
	    key += '&';
	    if(at)
		key += util::url_encode(at->secret);
	    /* TODO: do not build the whole subject */
	    string subject = method;
	    subject += '&';
	    string u = util::rfc_3986_normalize_uri(url);
	    string::size_type uco = u.find_first_of("#?");
	    if(uco!=string::npos) u.erase(uco);
	    subject += util::url_encode(u);
	    subject += '&';
	    subject += util::url_encode( fields.query_string() );
	    unsigned char md[SHA_DIGEST_LENGTH];
	    unsigned int md_len = 0;
	    HMAC( EVP_sha1(),
		    key.c_str(),key.size(),
		    (const unsigned char *)subject.c_str(),subject.size(),
		    md,&md_len );
	    assert(md_len==sizeof(md));
	    return util::encode_base64(md,md_len);
	}

	static void noquerize_url(string& url,const string& sepurl,basic_fields& f) {
	    string::size_type q = sepurl.find('?'),
		p = sepurl.find('#');
	    if(q==string::npos) {
		url = sepurl.substr(0,p);
	    }else{
		fields_t tmp;
		tmp.from_query(sepurl.substr(
			    q+1,
			    (p==string::npos)?string::npos:(p-q-q)));
		tmp.append_to(f);
		url = sepurl.substr(0,(p==string::npos)?q:min(p,q));
	    }
	}

	token_t basic_consumer::acquire_token(
		const service_endpoint_t& sep,
		const token_t* rt) {
	    util::curl_pick_t curl = util::curl_t::easy_init();
	    CURLcode r;
	    (r=curl.misc_sets())
		|| (r=curl.set_write());
	    if(r)
		throw exception_curl(OPKELE_CP_ "failed to set basic curly options",r);
	    http_request_t hr(
		    (sep.oauth_method==oauth_post_body)?"POST":"GET",
		    "");
	    fields_t uq;
	    noquerize_url(hr.url,sep.url,uq);
	    prepare_request(hr,uq,fields_t(),sep,rt);
	    switch(sep.oauth_method) {
		case oauth_auth_header:
		    throw opkele::not_implemented(OPKELE_CP_
			    "auth header for token acquisition isn't (yet?) supported");
		    break;
		case oauth_post_body:
		    (r=curl.easy_setopt(CURLOPT_POST,1))
			|| (r=curl.easy_setopt(CURLOPT_POSTFIELDS,hr.body.c_str()))
			|| (r=curl.easy_setopt(CURLOPT_POSTFIELDSIZE,hr.body.size()));
		    break;
		case oauth_url_query:
		    break;
		default:
		    throw opkele::exception(OPKELE_CP_ /* TODO: specialize */
			    "invalid oauth_method for request_token endpoint");
	    };
	    if(r)
		throw exception_curl(OPKELE_CP_ "failed to set curly options",r);
	    if( (r=curl.easy_setopt(CURLOPT_URL,hr.url.c_str())) )
		throw exception_curl(OPKELE_CP_ "failed to set curly urlie",r);
	    if( (r=curl.easy_perform()) )
		throw exception_curl(OPKELE_CP_ "failed to perform curly request",r);
	    token_t rv;
	    string::size_type p=0;
	    while(p!=string::npos) {
		string::size_type np = curl.response.find('&',p);
		string part;
		if(np==string::npos) {
		    part.assign(curl.response.c_str()+p); p = string::npos;
		}else{
		    part.assign(curl.response,p,np-p); p = np+1;
		}
		string::size_type eq = part.find('=');
		if(eq==string::npos) continue;
		string n(part,0,eq);
		if(n=="oauth_token") {
		    if(!rv.key.empty()) /* TODO: specialize */
			throw opkele::exception(OPKELE_CP_ "found oauth_token twice");
		    rv.key = util::url_decode(part.substr(eq+1));
		}else if(n=="oauth_token_secret") {
		    if(!rv.secret.empty()) /* TODO: specialize */
			throw opkele::exception(OPKELE_CP_ "found oauth_secret twice");
		    rv.secret = util::url_decode(part.substr(eq+1));
		}
	    }
	    return rv;
	}

	void basic_consumer::prepare_request(
		http_request_t& req,
		const basic_fields& qf,const basic_fields& pf,
		oauth_method_t om,const string& sm,
		const token_t *t,const string& realm) {
	    fields_t op;
	    op.set_field("oauth_consumer_key",consumer_token.key);
	    if(t) op.set_field("oauth_token",t->key);
	    op.set_field("oauth_signature_method",sm);
	    time_t now;
	    op.set_field("oauth_timestamp",
		    util::long_to_string(time(&now)));
	    op.set_field("oauth_nonce",allocate_nonce(now));
	    op.set_field("oauth_version","1.0");
	    /* TODO: normalize and strip down url */
	    {
		fields_t af; /* TODO: optimize, I don't want it to be copied */
		qf.copy_to(af); pf.append_to(af); op.append_to(af);
		op.set_field("oauth_signature", signature(
			    req.method,req.url,af,t) );
	    }
	    req.authorize_header.clear();
	    if(om==oauth_auth_header) {
		req.authorize_header = "OAuth ";
		req.authorize_header += "realm=\"";
		req.authorize_header += util::url_encode(realm);
		req.authorize_header += '\"';
		for(basic_fields::fields_iterator
			i=op.fields_begin(),ie=op.fields_end();
			i!=ie;++i) {
		    req.authorize_header += ", ";
		    req.authorize_header += *i;
		    req.authorize_header += "=\"";
		    req.authorize_header += util::url_encode(op.get_field(*i));
		    req.authorize_header += "\"";
		}
		req.url = qf.append_query(req.url);
		req.body = pf.query_string();
	    }else if(om==oauth_post_body) {
		assert(req.method=="POST");
		/* TODO: optimize, don't copy it over and over */
		fields_t p;
		pf.append_to(p); op.append_to(p);
		req.url = qf.append_query(req.url);
		req.body = p.query_string();
	    }else if(om==oauth_url_query) {
		fields_t q;
		qf.append_to(q); op.append_to(q);
		req.url = q.append_query(req.url);
		req.body = pf.query_string();
	    }else
		throw opkele::exception(OPKELE_CP_ /* TODO: specialize */
			"Unknown oauth method");
	}

	void basic_consumer::prepare_request(
		http_request_t& req,
		const basic_fields& qf,const basic_fields& pf,
		const service_endpoint_t& sep,
		const token_t *t,const string& realm) {
	    prepare_request(
		    req, qf, pf,
		    sep.oauth_method,sep.signature_method,
		    t,realm);
	}

	void http_request_t::setup_curl(CURL *curl) {
	    CURLcode r;
	    r = curl_easy_setopt(curl,CURLOPT_URL,url.c_str());
	    if(r)
		throw exception_curl(OPKELE_CP_ "failed to set curly urlie",r);
	    if(method=="POST") {
		(r = curl_easy_setopt(curl,CURLOPT_POST,1))
		    || (r = curl_easy_setopt(curl,CURLOPT_POSTFIELDS,body.c_str()))
		    || (r = curl_easy_setopt(curl,CURLOPT_POSTFIELDSIZE,body.size()));
	    }else if(method=="GET") {
		r = curl_easy_setopt(curl,CURLOPT_HTTPGET,1);
	    }else if(method=="HEAD") {
		r = curl_easy_setopt(curl,CURLOPT_NOBODY,1);
	    }else /* TODO: specialize exception */
		throw exception(OPKELE_CP_ "don't know how to handle http method");
	    if(r)
		throw exception_curl(OPKELE_CP_ "failed to set curly options",r);
	    if(!authorize_header.empty()) {
		r = curl_easy_setopt(curl,CURLOPT_HTTPHEADER,(curl_slist*)(
			    _curl_headers_list = curl_slist_append(
				0,string("Authorization: "+authorize_header).c_str()
				)
			    ) );
		if(r)
		    throw exception_curl(OPKELE_CP_ "failed to setup curlie header");
	    }
	}


	const basic_provider_endpoints& simple_consumer::get_endpoints() const {
	    return peps; }

	const string simple_consumer::allocate_nonce(time_t ts) {
#           ifndef HAVE_LIBUUID
		throw opkele::not_implemented(OPKELE_CP_
			"not implemented consumer's allocate_nonce()");
#           else /* HAVE_LIBUUID */
		uuid_t uuid; uuid_generate(uuid);
		return util::encode_base64(uuid,sizeof(uuid));
#           endif /* HAVE_LIBUUID */
	}

    }
}