├── .gitignore ├── __init__.py ├── oauth.py ├── sample.py └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | .project 4 | .pydevproject 5 | .settings 6 | 7 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikeknapp/AppEngine-OAuth-Library/c966cf0d935d8431e6e4f4c5c89436cd72bc89e3/__init__.py -------------------------------------------------------------------------------- /oauth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | A simple OAuth implementation for authenticating users with third party 5 | websites. 6 | 7 | A typical use case inside an AppEngine controller would be: 8 | 9 | 1) Create the OAuth client. In this case we'll use the Twitter client, 10 | but you could write other clients to connect to different services. 11 | 12 | import oauth 13 | 14 | consumer_key = "LKlkj83kaio2fjiudjd9...etc" 15 | consumer_secret = "58kdujslkfojkjsjsdk...etc" 16 | callback_url = "http://www.myurl.com/callback/twitter" 17 | 18 | client = oauth.TwitterClient(consumer_key, consumer_secret, callback_url) 19 | 20 | 2) Send the user to Twitter in order to login: 21 | 22 | self.redirect(client.get_authorization_url()) 23 | 24 | 3) Once the user has arrived back at your callback URL, you'll want to 25 | get the authenticated user information. 26 | 27 | auth_token = self.request.get("oauth_token") 28 | auth_verifier = self.request.get("oauth_verifier") 29 | user_info = client.get_user_info(auth_token, auth_verifier=auth_verifier) 30 | 31 | The "user_info" variable should then contain a dictionary of various 32 | user information (id, picture url, etc). What you do with that data is up 33 | to you. 34 | 35 | That's it! 36 | 37 | 4) If you need to, you can also call other other API URLs using 38 | client.make_request() as long as you supply a valid API URL and an access 39 | token and secret. Note, you may need to set method=urlfetch.POST. 40 | 41 | @author: Mike Knapp 42 | @copyright: Unrestricted. Feel free to use modify however you see fit. Please 43 | note however this software is unsupported. Please don't email me about it. :) 44 | """ 45 | 46 | from google.appengine.api import memcache 47 | from google.appengine.api import urlfetch 48 | from google.appengine.ext import db 49 | 50 | from cgi import parse_qs 51 | #ImportError: No module named django.utils 52 | #from django.utils import simplejson as json 53 | import json 54 | from hashlib import sha1 55 | from hmac import new as hmac 56 | from random import getrandbits 57 | from time import time 58 | from urllib import urlencode 59 | from urllib import quote as urlquote 60 | from urllib import unquote as urlunquote 61 | 62 | import logging 63 | 64 | 65 | TWITTER = "twitter" 66 | YAHOO = "yahoo" 67 | MYSPACE = "myspace" 68 | DROPBOX = "dropbox" 69 | LINKEDIN = "linkedin" 70 | YAMMER = "yammer" 71 | 72 | 73 | class OAuthException(Exception): 74 | pass 75 | 76 | 77 | def get_oauth_client(service, key, secret, callback_url): 78 | """Get OAuth Client. 79 | 80 | A factory that will return the appropriate OAuth client. 81 | """ 82 | 83 | if service == TWITTER: 84 | return TwitterClient(key, secret, callback_url) 85 | elif service == YAHOO: 86 | return YahooClient(key, secret, callback_url) 87 | elif service == MYSPACE: 88 | return MySpaceClient(key, secret, callback_url) 89 | elif service == DROPBOX: 90 | return DropboxClient(key, secret, callback_url) 91 | elif service == LINKEDIN: 92 | return LinkedInClient(key, secret, callback_url) 93 | elif service == YAMMER: 94 | return YammerClient(key, secret, callback_url) 95 | else: 96 | raise Exception, "Unknown OAuth service %s" % service 97 | 98 | 99 | class AuthToken(db.Model): 100 | """Auth Token. 101 | 102 | A temporary auth token that we will use to authenticate a user with a 103 | third party website. (We need to store the data while the user visits 104 | the third party website to authenticate themselves.) 105 | 106 | TODO: Implement a cron to clean out old tokens periodically. 107 | """ 108 | 109 | service = db.StringProperty(required=True) 110 | token = db.StringProperty(required=True) 111 | secret = db.StringProperty(required=True) 112 | created = db.DateTimeProperty(auto_now_add=True) 113 | 114 | 115 | class OAuthClient(): 116 | 117 | def __init__(self, service_name, consumer_key, consumer_secret, request_url, 118 | access_url, callback_url=None): 119 | """ Constructor.""" 120 | 121 | self.service_name = service_name 122 | self.consumer_key = consumer_key 123 | self.consumer_secret = consumer_secret 124 | self.request_url = request_url 125 | self.access_url = access_url 126 | self.callback_url = callback_url 127 | 128 | def prepare_request(self, url, token="", secret="", additional_params=None, 129 | method=urlfetch.GET, t=None, nonce=None): 130 | """Prepare Request. 131 | 132 | Prepares an authenticated request to any OAuth protected resource. 133 | 134 | Returns the payload of the request. 135 | """ 136 | 137 | def encode(text): 138 | return urlquote(str(text), "~") 139 | 140 | params = { 141 | "oauth_consumer_key": self.consumer_key, 142 | "oauth_signature_method": "HMAC-SHA1", 143 | "oauth_timestamp": t if t else str(int(time())), 144 | "oauth_nonce": nonce if nonce else str(getrandbits(64)), 145 | "oauth_version": "1.0" 146 | } 147 | 148 | if token: 149 | params["oauth_token"] = token 150 | elif self.callback_url: 151 | params["oauth_callback"] = self.callback_url 152 | 153 | if additional_params: 154 | params.update(additional_params) 155 | 156 | for k,v in params.items(): 157 | if isinstance(v, unicode): 158 | params[k] = v.encode('utf8') 159 | 160 | # Join all of the params together. 161 | params_str = "&".join(["%s=%s" % (encode(k), encode(params[k])) 162 | for k in sorted(params)]) 163 | 164 | # Join the entire message together per the OAuth specification. 165 | message = "&".join(["GET" if method == urlfetch.GET else "POST", 166 | encode(url), encode(params_str)]) 167 | 168 | # Create a HMAC-SHA1 signature of the message. 169 | key = "%s&%s" % (self.consumer_secret, secret) # Note compulsory "&". 170 | signature = hmac(key, message, sha1) 171 | digest_base64 = signature.digest().encode("base64").strip() 172 | params["oauth_signature"] = digest_base64 173 | 174 | # Construct the request payload and return it 175 | return urlencode(params) 176 | 177 | def make_async_request(self, url, token="", secret="", additional_params=None, 178 | protected=False, method=urlfetch.GET, headers={}): 179 | """Make Request. 180 | 181 | Make an authenticated request to any OAuth protected resource. 182 | 183 | If protected is equal to True, the Authorization: OAuth header will be set. 184 | 185 | A urlfetch response object is returned. 186 | """ 187 | 188 | payload = self.prepare_request(url, token, secret, additional_params, 189 | method) 190 | 191 | if method == urlfetch.GET: 192 | url = "%s?%s" % (url, payload) 193 | payload = None 194 | 195 | if protected: 196 | headers["Authorization"] = "OAuth" 197 | 198 | rpc = urlfetch.create_rpc(deadline=10.0) 199 | urlfetch.make_fetch_call(rpc, url, method=method, headers=headers, 200 | payload=payload) 201 | return rpc 202 | 203 | def make_request(self, url, token="", secret="", additional_params=None, 204 | protected=False, method=urlfetch.GET, headers={}): 205 | 206 | return self.make_async_request(url, token, secret, additional_params, 207 | protected, method, headers).get_result() 208 | 209 | def get_authorization_url(self): 210 | """Get Authorization URL. 211 | 212 | Returns a service specific URL which contains an auth token. The user 213 | should be redirected to this URL so that they can give consent to be 214 | logged in. 215 | """ 216 | 217 | raise NotImplementedError, "Must be implemented by a subclass" 218 | 219 | def get_user_info(self, auth_token, auth_verifier=""): 220 | """Get User Info. 221 | 222 | Exchanges the auth token for an access token and returns a dictionary 223 | of information about the authenticated user. 224 | """ 225 | 226 | auth_token = urlunquote(auth_token) 227 | auth_verifier = urlunquote(auth_verifier) 228 | 229 | auth_secret = memcache.get(self._get_memcache_auth_key(auth_token)) 230 | 231 | if not auth_secret: 232 | result = AuthToken.gql(""" 233 | WHERE 234 | service = :1 AND 235 | token = :2 236 | LIMIT 237 | 1 238 | """, self.service_name, auth_token).get() 239 | 240 | if not result: 241 | logging.error("The auth token %s was not found in our db" % auth_token) 242 | raise Exception, "Could not find Auth Token in database" 243 | else: 244 | auth_secret = result.secret 245 | 246 | response = self.make_request(self.access_url, 247 | token=auth_token, 248 | secret=auth_secret, 249 | additional_params={"oauth_verifier": 250 | auth_verifier}) 251 | 252 | # Extract the access token/secret from the response. 253 | result = self._extract_credentials(response) 254 | 255 | # Try to collect some information about this user from the service. 256 | user_info = self._lookup_user_info(result["token"], result["secret"]) 257 | user_info.update(result) 258 | 259 | return user_info 260 | 261 | def _get_auth_token(self): 262 | """Get Authorization Token. 263 | 264 | Actually gets the authorization token and secret from the service. The 265 | token and secret are stored in our database, and the auth token is 266 | returned. 267 | """ 268 | 269 | response = self.make_request(self.request_url) 270 | result = self._extract_credentials(response) 271 | 272 | auth_token = result["token"] 273 | auth_secret = result["secret"] 274 | 275 | # Save the auth token and secret in our database. 276 | auth = AuthToken(service=self.service_name, 277 | token=auth_token, 278 | secret=auth_secret) 279 | auth.put() 280 | 281 | # Add the secret to memcache as well. 282 | memcache.set(self._get_memcache_auth_key(auth_token), auth_secret, 283 | time=20*60) 284 | 285 | return auth_token 286 | 287 | def _get_memcache_auth_key(self, auth_token): 288 | 289 | return "oauth_%s_%s" % (self.service_name, auth_token) 290 | 291 | def _extract_credentials(self, result): 292 | """Extract Credentials. 293 | 294 | Returns an dictionary containing the token and secret (if present). 295 | Throws an Exception otherwise. 296 | """ 297 | 298 | token = None 299 | secret = None 300 | parsed_results = parse_qs(result.content) 301 | 302 | if "oauth_token" in parsed_results: 303 | token = parsed_results["oauth_token"][0] 304 | 305 | if "oauth_token_secret" in parsed_results: 306 | secret = parsed_results["oauth_token_secret"][0] 307 | 308 | if not (token and secret) or result.status_code != 200: 309 | logging.error("Could not extract token/secret: %s" % result.content) 310 | raise OAuthException("Problem talking to the service") 311 | 312 | return { 313 | "service": self.service_name, 314 | "token": token, 315 | "secret": secret 316 | } 317 | 318 | def _lookup_user_info(self, access_token, access_secret): 319 | """Lookup User Info. 320 | 321 | Complies a dictionary describing the user. The user should be 322 | authenticated at this point. Each different client should override 323 | this method. 324 | """ 325 | 326 | raise NotImplementedError, "Must be implemented by a subclass" 327 | 328 | def _get_default_user_info(self): 329 | """Get Default User Info. 330 | 331 | Returns a blank array that can be used to populate generalized user 332 | information. 333 | """ 334 | 335 | return { 336 | "id": "", 337 | "username": "", 338 | "name": "", 339 | "picture": "" 340 | } 341 | 342 | 343 | class TwitterClient(OAuthClient): 344 | """Twitter Client. 345 | 346 | A client for talking to the Twitter API using OAuth as the 347 | authentication model. 348 | """ 349 | 350 | def __init__(self, consumer_key, consumer_secret, callback_url): 351 | """Constructor.""" 352 | 353 | OAuthClient.__init__(self, 354 | TWITTER, 355 | consumer_key, 356 | consumer_secret, 357 | "https://api.twitter.com/oauth/request_token", 358 | "https://api.twitter.com/oauth/access_token", 359 | callback_url) 360 | 361 | def get_authorization_url(self): 362 | """Get Authorization URL.""" 363 | 364 | token = self._get_auth_token() 365 | return "https://api.twitter.com/oauth/authorize?oauth_token=%s" % token 366 | 367 | def get_authenticate_url(self): 368 | """Get Authentication URL.""" 369 | token = self._get_auth_token() 370 | return "https://api.twitter.com/oauth/authenticate?oauth_token=%s" % token 371 | 372 | def _lookup_user_info(self, access_token, access_secret): 373 | """Lookup User Info. 374 | 375 | Lookup the user on Twitter. 376 | """ 377 | 378 | response = self.make_request( 379 | "https://api.twitter.com/1.1/account/verify_credentials.json", 380 | token=access_token, secret=access_secret, protected=True) 381 | 382 | data = json.loads(response.content) 383 | 384 | user_info = self._get_default_user_info() 385 | user_info["id"] = data["id"] 386 | user_info["username"] = data["screen_name"] 387 | user_info["name"] = data["name"] 388 | user_info["picture"] = data["profile_image_url"] 389 | 390 | return user_info 391 | 392 | 393 | class MySpaceClient(OAuthClient): 394 | """MySpace Client. 395 | 396 | A client for talking to the MySpace API using OAuth as the 397 | authentication model. 398 | """ 399 | 400 | def __init__(self, consumer_key, consumer_secret, callback_url): 401 | """Constructor.""" 402 | 403 | OAuthClient.__init__(self, 404 | MYSPACE, 405 | consumer_key, 406 | consumer_secret, 407 | "http://api.myspace.com/request_token", 408 | "http://api.myspace.com/access_token", 409 | callback_url) 410 | 411 | def get_authorization_url(self): 412 | """Get Authorization URL.""" 413 | 414 | token = self._get_auth_token() 415 | return ("http://api.myspace.com/authorize?oauth_token=%s" 416 | "&oauth_callback=%s" % (token, urlquote(self.callback_url))) 417 | 418 | def _lookup_user_info(self, access_token, access_secret): 419 | """Lookup User Info. 420 | 421 | Lookup the user on MySpace. 422 | """ 423 | 424 | response = self.make_request("http://api.myspace.com/v1/user.json", 425 | token=access_token, secret=access_secret, protected=True) 426 | 427 | data = json.loads(response.content) 428 | 429 | user_info = self._get_default_user_info() 430 | user_info["id"] = data["userId"] 431 | username = data["webUri"].replace("http://www.myspace.com/", "") 432 | user_info["username"] = username 433 | user_info["name"] = data["name"] 434 | user_info["picture"] = data["image"] 435 | 436 | return user_info 437 | 438 | 439 | class YahooClient(OAuthClient): 440 | """Yahoo! Client. 441 | 442 | A client for talking to the Yahoo! API using OAuth as the 443 | authentication model. 444 | """ 445 | 446 | def __init__(self, consumer_key, consumer_secret, callback_url): 447 | """Constructor.""" 448 | 449 | OAuthClient.__init__(self, 450 | YAHOO, 451 | consumer_key, 452 | consumer_secret, 453 | "https://api.login.yahoo.com/oauth/v2/get_request_token", 454 | "https://api.login.yahoo.com/oauth/v2/get_token", 455 | callback_url) 456 | 457 | def get_authorization_url(self): 458 | """Get Authorization URL.""" 459 | 460 | token = self._get_auth_token() 461 | return ("https://api.login.yahoo.com/oauth/v2/request_auth?oauth_token=%s" 462 | % token) 463 | 464 | def _lookup_user_info(self, access_token, access_secret): 465 | """Lookup User Info. 466 | 467 | Lookup the user on Yahoo! 468 | """ 469 | 470 | user_info = self._get_default_user_info() 471 | 472 | # 1) Obtain the user's GUID. 473 | response = self.make_request( 474 | "http://social.yahooapis.com/v1/me/guid", token=access_token, 475 | secret=access_secret, additional_params={"format": "json"}, 476 | protected=True) 477 | 478 | data = json.loads(response.content)["guid"] 479 | guid = data["value"] 480 | 481 | # 2) Inspect the user's profile. 482 | response = self.make_request( 483 | "http://social.yahooapis.com/v1/user/%s/profile/usercard" % guid, 484 | token=access_token, secret=access_secret, 485 | additional_params={"format": "json"}, protected=True) 486 | 487 | data = json.loads(response.content)["profile"] 488 | 489 | user_info["id"] = guid 490 | user_info["username"] = data["nickname"].lower() 491 | user_info["name"] = data["nickname"] 492 | user_info["picture"] = data["image"]["imageUrl"] 493 | 494 | return user_info 495 | 496 | 497 | class DropboxClient(OAuthClient): 498 | """Dropbox Client. 499 | 500 | A client for talking to the Dropbox API using OAuth as the authentication 501 | model. 502 | """ 503 | 504 | def __init__(self, consumer_key, consumer_secret, callback_url): 505 | """Constructor.""" 506 | 507 | OAuthClient.__init__(self, 508 | DROPBOX, 509 | consumer_key, 510 | consumer_secret, 511 | "https://api.dropbox.com/0/oauth/request_token", 512 | "https://api.dropbox.com/0/oauth/access_token", 513 | callback_url) 514 | 515 | def get_authorization_url(self): 516 | """Get Authorization URL.""" 517 | 518 | token = self._get_auth_token() 519 | return ("http://www.dropbox.com/0/oauth/authorize?" 520 | "oauth_token=%s&oauth_callback=%s" % (token, 521 | urlquote(self.callback_url))) 522 | 523 | def _lookup_user_info(self, access_token, access_secret): 524 | """Lookup User Info. 525 | 526 | Lookup the user on Dropbox. 527 | """ 528 | 529 | response = self.make_request("http://api.dropbox.com/0/account/info", 530 | token=access_token, secret=access_secret, 531 | protected=True) 532 | 533 | data = json.loads(response.content) 534 | user_info = self._get_default_user_info() 535 | user_info["id"] = data["uid"] 536 | user_info["name"] = data["display_name"] 537 | user_info["country"] = data["country"] 538 | 539 | return user_info 540 | 541 | 542 | class LinkedInClient(OAuthClient): 543 | """LinkedIn Client. 544 | 545 | A client for talking to the LinkedIn API using OAuth as the 546 | authentication model. 547 | """ 548 | 549 | def __init__(self, consumer_key, consumer_secret, callback_url): 550 | """Constructor.""" 551 | 552 | OAuthClient.__init__(self, 553 | LINKEDIN, 554 | consumer_key, 555 | consumer_secret, 556 | "https://api.linkedin.com/uas/oauth/requestToken", 557 | "https://api.linkedin.com/uas/oauth/accessToken", 558 | callback_url) 559 | 560 | def get_authorization_url(self): 561 | """Get Authorization URL.""" 562 | 563 | token = self._get_auth_token() 564 | return ("https://www.linkedin.com/uas/oauth/authenticate?oauth_token=%s" 565 | "&oauth_callback=%s" % (token, urlquote(self.callback_url))) 566 | 567 | def _lookup_user_info(self, access_token, access_secret): 568 | """Lookup User Info. 569 | 570 | Lookup the user on LinkedIn 571 | """ 572 | 573 | user_info = self._get_default_user_info() 574 | 575 | # Grab the user's profile from LinkedIn. 576 | response = self.make_request("http://api.linkedin.com/v1/people/~:" 577 | "(picture-url,id,first-name,last-name)", 578 | token=access_token, 579 | secret=access_secret, 580 | protected=False, 581 | headers={"x-li-format":"json"}) 582 | 583 | data = json.loads(response.content) 584 | user_info["id"] = data["id"] 585 | user_info["picture"] = data["pictureUrl"] 586 | user_info["name"] = data["firstName"] + " " + data["lastName"] 587 | return user_info 588 | 589 | 590 | class YammerClient(OAuthClient): 591 | """Yammer Client. 592 | 593 | A client for talking to the Yammer API using OAuth as the 594 | authentication model. 595 | """ 596 | 597 | def __init__(self, consumer_key, consumer_secret, callback_url): 598 | """Constructor.""" 599 | 600 | OAuthClient.__init__(self, 601 | YAMMER, 602 | consumer_key, 603 | consumer_secret, 604 | "https://www.yammer.com/oauth/request_token", 605 | "https://www.yammer.com/oauth/access_token", 606 | callback_url) 607 | 608 | def get_authorization_url(self): 609 | """Get Authorization URL.""" 610 | 611 | token = self._get_auth_token() 612 | return ("https://www.yammer.com/oauth/authorize?oauth_token=%s" 613 | "&oauth_callback=%s" % (token, urlquote(self.callback_url))) 614 | 615 | def _lookup_user_info(self, access_token, access_secret): 616 | """Lookup User Info. 617 | 618 | Lookup the user on Yammer 619 | """ 620 | 621 | user_info = self._get_default_user_info() 622 | 623 | # Grab the user's profile from Yammer. 624 | response = self.make_request("https://www.yammer.com/api/v1/users/current.json", 625 | token=access_token, 626 | secret=access_secret, 627 | protected=False, 628 | headers={"x-li-format":"json"}) 629 | 630 | data = json.loads(response.content) 631 | user_info = self._get_default_user_info() 632 | user_info["id"] = data["name"] 633 | user_info["picture"] = data["mugshot_url"] 634 | user_info["name"] = data["full_name"] 635 | return user_info 636 | -------------------------------------------------------------------------------- /sample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This is an sample AppEngine application that shows how to 1) log in a user 4 | # using the Twitter OAuth API and 2) extract their timeline. 5 | # 6 | # INSTRUCTIONS: 7 | # 8 | # 1. Set up a new AppEngine application using this file, let's say on port 9 | # 8080. Rename this file to main.py, or alternatively modify your app.yaml 10 | # file.) 11 | # 2. Fill in the application ("consumer") key and secret lines below. 12 | # 3. Visit http://localhost:8080 and click the "login" link to be redirected 13 | # to Twitter.com. 14 | # 4. Once verified, you'll be redirected back to your app on localhost and 15 | # you'll see some of your Twitter user info printed in the browser. 16 | # 5. Copy and paste the token and secret info into this file, replacing the 17 | # default values for user_token and user_secret. You'll need the user's token 18 | # & secret info to interact with the Twitter API on their behalf from now on. 19 | # 6. Finally, visit http://localhost:8080/timeline to see your twitter 20 | # timeline. 21 | # 22 | 23 | __author__ = "Mike Knapp" 24 | 25 | import oauth 26 | 27 | from google.appengine.ext import webapp 28 | from google.appengine.ext.webapp import util 29 | 30 | 31 | class MainHandler(webapp.RequestHandler): 32 | 33 | def get(self, mode=""): 34 | 35 | # Your application Twitter application ("consumer") key and secret. 36 | # You'll need to register an application on Twitter first to get this 37 | # information: http://www.twitter.com/oauth 38 | application_key = "FILL_IN" 39 | application_secret = "FILL_IN" 40 | 41 | # Fill in the next 2 lines after you have successfully logged in to 42 | # Twitter per the instructions above. This is the *user's* token and 43 | # secret. You need these values to call the API on their behalf after 44 | # they have logged in to your app. 45 | user_token = "FILL_IN" 46 | user_secret = "FILL_IN" 47 | 48 | # In the real world, you'd want to edit this callback URL to point to your 49 | # production server. This is where the user is sent to after they have 50 | # authenticated with Twitter. 51 | callback_url = "%s/verify" % self.request.host_url 52 | 53 | client = oauth.TwitterClient(application_key, application_secret, 54 | callback_url) 55 | 56 | if mode == "login": 57 | return self.redirect(client.get_authorization_url()) 58 | 59 | if mode == "verify": 60 | auth_token = self.request.get("oauth_token") 61 | auth_verifier = self.request.get("oauth_verifier") 62 | user_info = client.get_user_info(auth_token, auth_verifier=auth_verifier) 63 | return self.response.out.write(user_info) 64 | 65 | if mode == "timeline": 66 | timeline_url = "http://twitter.com/statuses/user_timeline.xml" 67 | result = client.make_request(url=timeline_url, token=user_token, 68 | secret=user_secret) 69 | return self.response.out.write(result.content) 70 | 71 | self.response.out.write("Login via Twitter") 72 | 73 | def main(): 74 | application = webapp.WSGIApplication([('/(.*)', MainHandler)], 75 | debug=True) 76 | util.run_wsgi_app(application) 77 | 78 | 79 | if __name__ == '__main__': 80 | main() 81 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import oauth 4 | 5 | from google.appengine.api import urlfetch 6 | 7 | import unittest 8 | 9 | 10 | class TestOAuth(unittest.TestCase): 11 | """Test our OAuth code""" 12 | 13 | def setUp(self): 14 | 15 | self.service_name = oauth.TWITTER 16 | self.consumer_key = "consumer_key" 17 | self.consumer_secret = "consumer_secret" 18 | self.request_url = "http://www.twitter.com/fake/request" 19 | self.access_url = "http://www.twitter.com/fake/access" 20 | self.callback_url = "http://www.twitter.com/fake/callback" 21 | 22 | self.client = oauth.OAuthClient(self.service_name, 23 | self.consumer_key, 24 | self.consumer_secret, 25 | self.request_url, 26 | self.access_url, 27 | self.callback_url) 28 | 29 | def tearDown(self): 30 | 31 | pass 32 | 33 | def test_client_factory(self): 34 | 35 | result = oauth.get_oauth_client(oauth.TWITTER, "key", "secret", 36 | "http://t.com/callback") 37 | 38 | self.assert_(isinstance(result,oauth.TwitterClient)) 39 | self.assertEquals(result.service_name, oauth.TWITTER) 40 | self.assertEquals(result.consumer_key, "key") 41 | self.assertEquals(result.consumer_secret, "secret") 42 | self.assertEquals(result.callback_url, "http://t.com/callback") 43 | 44 | def test_initialise(self): 45 | 46 | self.assertEquals(self.client.service_name, self.service_name) 47 | self.assertEquals(self.client.consumer_key, self.consumer_key) 48 | self.assertEquals(self.client.consumer_secret, self.consumer_secret) 49 | self.assertEquals(self.client.request_url, self.request_url) 50 | self.assertEquals(self.client.access_url, self.access_url) 51 | 52 | def test_prepare_request(self): 53 | 54 | result = self.client.prepare_request("http://www.twitter.com/fake/request", 55 | t=123456789, 56 | nonce="jh23jk4h763u3") 57 | self.assertEquals(result, 58 | ("oauth_nonce=jh23jk4h763u3&" 59 | "oauth_timestamp=123456789&" 60 | "oauth_consumer_key=consumer_key&" 61 | "oauth_signature_method=HMAC-SHA1&" 62 | "oauth_version=1.0&" 63 | "oauth_signature=" 64 | "dB1UU6FF7WChGPF4Ja5M%2FI0WRFg%3D&" 65 | "oauth_callback=" 66 | "http%3A%2F%2Fwww.twitter.com%2Ffake%2Fcallback") 67 | ) 68 | 69 | 70 | if __name__ == "__main__": 71 | unittest.main() 72 | --------------------------------------------------------------------------------