mirror of
https://github.com/fergalmoran/Chrome2Kindle.git
synced 2025-12-22 09:38:38 +00:00
Merge branch 'master' of github.com:fergalmoran/Chrome2Kindle
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
from google.appengine.ext import webapp
|
||||
from google.appengine.ext.webapp.util import run_wsgi_app
|
||||
from django.utils import simplejson
|
||||
import logging, pdb, traceback
|
||||
from PdfMaker import PdfMaker
|
||||
from MailInterface import MailInterface
|
||||
from HtmlParser import HtmlParser
|
||||
from django.utils import simplejson
|
||||
from model.Hits import SiteStats
|
||||
|
||||
class ExtensionPostHandler(webapp.RequestHandler):
|
||||
|
||||
@@ -33,6 +33,12 @@ class ExtensionPostHandler(webapp.RequestHandler):
|
||||
logging.info('JSON Result: ' + simplejson.dumps(jsonResult));
|
||||
self.response.out.write(simplejson.dumps(jsonResult));
|
||||
|
||||
new = SiteStats(
|
||||
source = self.request.remote_addr,
|
||||
action = 'ExtensionPostHandler/post',
|
||||
headers = self.request.headers
|
||||
)
|
||||
|
||||
except Exception, ex:
|
||||
logging.error('Error generating pdf: ' + str(ex.message))
|
||||
logging.error(traceback.format_exc())
|
||||
|
||||
13
server/OAuthHandler.py
Normal file
13
server/OAuthHandler.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from oauth import oauth
|
||||
from google.appengine.ext import webapp
|
||||
import logging
|
||||
|
||||
class OAuthHandler(webapp.RequestHandler):
|
||||
def request(self):
|
||||
logging.debug("request")
|
||||
|
||||
def authorise(self):
|
||||
logging.debug("request")
|
||||
|
||||
def access(self):
|
||||
logging.debug("request")
|
||||
@@ -7,10 +7,12 @@ from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from ExtensionPostHandler import ExtensionPostHandler
|
||||
from SiteHandler import SiteHandler
|
||||
from OAuthHandler import OAuthHandler
|
||||
|
||||
application = webapp.WSGIApplication(
|
||||
[('/convert', ExtensionPostHandler),
|
||||
('/', SiteHandler)],
|
||||
application = webapp.WSGIApplication([
|
||||
('/convert', ExtensionPostHandler),
|
||||
('/auth', OAuthHandler),
|
||||
('/', SiteHandler)],
|
||||
debug=True)
|
||||
|
||||
def main():
|
||||
|
||||
9
server/model/Hits.py
Normal file
9
server/model/Hits.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from google.appengine.ext import db
|
||||
|
||||
class SiteStats(db.Model):
|
||||
source = db.StringProperty(required = True)
|
||||
action = db.StringProperty(required = True)
|
||||
headers = db.StringProperty(required = True)
|
||||
|
||||
timestamp = db.DateTimeProperty(auto_now_add = True)
|
||||
|
||||
0
server/model/__init__.py
Normal file
0
server/model/__init__.py
Normal file
0
server/oauth/__init__.py
Normal file
0
server/oauth/__init__.py
Normal file
165
server/oauth/example/client.py
Normal file
165
server/oauth/example/client.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2007 Leah Culver
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Example consumer. This is not recommended for production.
|
||||
Instead, you'll want to create your own subclass of OAuthClient
|
||||
or find one that works with your web framework.
|
||||
"""
|
||||
|
||||
import httplib
|
||||
import time
|
||||
import oauth.oauth as oauth
|
||||
|
||||
# settings for the local test consumer
|
||||
SERVER = 'localhost'
|
||||
PORT = 8080
|
||||
|
||||
# fake urls for the test server (matches ones in server.py)
|
||||
REQUEST_TOKEN_URL = 'https://photos.example.net/request_token'
|
||||
ACCESS_TOKEN_URL = 'https://photos.example.net/access_token'
|
||||
AUTHORIZATION_URL = 'https://photos.example.net/authorize'
|
||||
CALLBACK_URL = 'http://printer.example.com/request_token_ready'
|
||||
RESOURCE_URL = 'http://photos.example.net/photos'
|
||||
|
||||
# key and secret granted by the service provider for this consumer application - same as the MockOAuthDataStore
|
||||
CONSUMER_KEY = 'key'
|
||||
CONSUMER_SECRET = 'secret'
|
||||
|
||||
# example client using httplib with headers
|
||||
class SimpleOAuthClient(oauth.OAuthClient):
|
||||
|
||||
def __init__(self, server, port=httplib.HTTP_PORT, request_token_url='', access_token_url='', authorization_url=''):
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.request_token_url = request_token_url
|
||||
self.access_token_url = access_token_url
|
||||
self.authorization_url = authorization_url
|
||||
self.connection = httplib.HTTPConnection("%s:%d" % (self.server, self.port))
|
||||
|
||||
def fetch_request_token(self, oauth_request):
|
||||
# via headers
|
||||
# -> OAuthToken
|
||||
self.connection.request(oauth_request.http_method, self.request_token_url, headers=oauth_request.to_header())
|
||||
response = self.connection.getresponse()
|
||||
return oauth.OAuthToken.from_string(response.read())
|
||||
|
||||
def fetch_access_token(self, oauth_request):
|
||||
# via headers
|
||||
# -> OAuthToken
|
||||
self.connection.request(oauth_request.http_method, self.access_token_url, headers=oauth_request.to_header())
|
||||
response = self.connection.getresponse()
|
||||
return oauth.OAuthToken.from_string(response.read())
|
||||
|
||||
def authorize_token(self, oauth_request):
|
||||
# via url
|
||||
# -> typically just some okay response
|
||||
self.connection.request(oauth_request.http_method, oauth_request.to_url())
|
||||
response = self.connection.getresponse()
|
||||
return response.read()
|
||||
|
||||
def access_resource(self, oauth_request):
|
||||
# via post body
|
||||
# -> some protected resources
|
||||
headers = {'Content-Type' :'application/x-www-form-urlencoded'}
|
||||
self.connection.request('POST', RESOURCE_URL, body=oauth_request.to_postdata(), headers=headers)
|
||||
response = self.connection.getresponse()
|
||||
return response.read()
|
||||
|
||||
def run_example():
|
||||
|
||||
# setup
|
||||
print '** OAuth Python Library Example **'
|
||||
client = SimpleOAuthClient(SERVER, PORT, REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZATION_URL)
|
||||
consumer = oauth.OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET)
|
||||
signature_method_plaintext = oauth.OAuthSignatureMethod_PLAINTEXT()
|
||||
signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()
|
||||
pause()
|
||||
|
||||
# get request token
|
||||
print '* Obtain a request token ...'
|
||||
pause()
|
||||
oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, callback=CALLBACK_URL, http_url=client.request_token_url)
|
||||
oauth_request.sign_request(signature_method_plaintext, consumer, None)
|
||||
print 'REQUEST (via headers)'
|
||||
print 'parameters: %s' % str(oauth_request.parameters)
|
||||
pause()
|
||||
token = client.fetch_request_token(oauth_request)
|
||||
print 'GOT'
|
||||
print 'key: %s' % str(token.key)
|
||||
print 'secret: %s' % str(token.secret)
|
||||
print 'callback confirmed? %s' % str(token.callback_confirmed)
|
||||
pause()
|
||||
|
||||
print '* Authorize the request token ...'
|
||||
pause()
|
||||
oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=client.authorization_url)
|
||||
print 'REQUEST (via url query string)'
|
||||
print 'parameters: %s' % str(oauth_request.parameters)
|
||||
pause()
|
||||
# this will actually occur only on some callback
|
||||
response = client.authorize_token(oauth_request)
|
||||
print 'GOT'
|
||||
print response
|
||||
# sad way to get the verifier
|
||||
import urlparse, cgi
|
||||
query = urlparse.urlparse(response)[4]
|
||||
params = cgi.parse_qs(query, keep_blank_values=False)
|
||||
verifier = params['oauth_verifier'][0]
|
||||
print 'verifier: %s' % verifier
|
||||
pause()
|
||||
|
||||
# get access token
|
||||
print '* Obtain an access token ...'
|
||||
pause()
|
||||
oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, verifier=verifier, http_url=client.access_token_url)
|
||||
oauth_request.sign_request(signature_method_plaintext, consumer, token)
|
||||
print 'REQUEST (via headers)'
|
||||
print 'parameters: %s' % str(oauth_request.parameters)
|
||||
pause()
|
||||
token = client.fetch_access_token(oauth_request)
|
||||
print 'GOT'
|
||||
print 'key: %s' % str(token.key)
|
||||
print 'secret: %s' % str(token.secret)
|
||||
pause()
|
||||
|
||||
# access some protected resources
|
||||
print '* Access protected resources ...'
|
||||
pause()
|
||||
parameters = {'file': 'vacation.jpg', 'size': 'original'} # resource specific params
|
||||
oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, http_method='POST', http_url=RESOURCE_URL, parameters=parameters)
|
||||
oauth_request.sign_request(signature_method_hmac_sha1, consumer, token)
|
||||
print 'REQUEST (via post body)'
|
||||
print 'parameters: %s' % str(oauth_request.parameters)
|
||||
pause()
|
||||
params = client.access_resource(oauth_request)
|
||||
print 'GOT'
|
||||
print 'non-oauth parameters: %s' % params
|
||||
pause()
|
||||
|
||||
def pause():
|
||||
print ''
|
||||
time.sleep(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_example()
|
||||
print 'Done.'
|
||||
195
server/oauth/example/server.py
Normal file
195
server/oauth/example/server.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2007 Leah Culver
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||
import urllib
|
||||
|
||||
import oauth.oauth as oauth
|
||||
|
||||
# fake urls for the test server
|
||||
REQUEST_TOKEN_URL = 'https://photos.example.net/request_token'
|
||||
ACCESS_TOKEN_URL = 'https://photos.example.net/access_token'
|
||||
AUTHORIZATION_URL = 'https://photos.example.net/authorize'
|
||||
CALLBACK_URL = 'http://printer.example.com/request_token_ready'
|
||||
RESOURCE_URL = 'http://photos.example.net/photos'
|
||||
REALM = 'http://photos.example.net/'
|
||||
VERIFIER = 'verifier'
|
||||
|
||||
# example store for one of each thing
|
||||
class MockOAuthDataStore(oauth.OAuthDataStore):
|
||||
|
||||
def __init__(self):
|
||||
self.consumer = oauth.OAuthConsumer('key', 'secret')
|
||||
self.request_token = oauth.OAuthToken('requestkey', 'requestsecret')
|
||||
self.access_token = oauth.OAuthToken('accesskey', 'accesssecret')
|
||||
self.nonce = 'nonce'
|
||||
self.verifier = VERIFIER
|
||||
|
||||
def lookup_consumer(self, key):
|
||||
if key == self.consumer.key:
|
||||
return self.consumer
|
||||
return None
|
||||
|
||||
def lookup_token(self, token_type, token):
|
||||
token_attrib = getattr(self, '%s_token' % token_type)
|
||||
if token == token_attrib.key:
|
||||
## HACK
|
||||
token_attrib.set_callback(CALLBACK_URL)
|
||||
return token_attrib
|
||||
return None
|
||||
|
||||
def lookup_nonce(self, oauth_consumer, oauth_token, nonce):
|
||||
if oauth_token and oauth_consumer.key == self.consumer.key and (oauth_token.key == self.request_token.key or oauth_token.key == self.access_token.key) and nonce == self.nonce:
|
||||
return self.nonce
|
||||
return None
|
||||
|
||||
def fetch_request_token(self, oauth_consumer, oauth_callback):
|
||||
if oauth_consumer.key == self.consumer.key:
|
||||
if oauth_callback:
|
||||
# want to check here if callback is sensible
|
||||
# for mock store, we assume it is
|
||||
self.request_token.set_callback(oauth_callback)
|
||||
return self.request_token
|
||||
return None
|
||||
|
||||
def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier):
|
||||
if oauth_consumer.key == self.consumer.key and oauth_token.key == self.request_token.key and oauth_verifier == self.verifier:
|
||||
# want to check here if token is authorized
|
||||
# for mock store, we assume it is
|
||||
return self.access_token
|
||||
return None
|
||||
|
||||
def authorize_request_token(self, oauth_token, user):
|
||||
if oauth_token.key == self.request_token.key:
|
||||
# authorize the request token in the store
|
||||
# for mock store, do nothing
|
||||
return self.request_token
|
||||
return None
|
||||
|
||||
class RequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.oauth_server = oauth.OAuthServer(MockOAuthDataStore())
|
||||
self.oauth_server.add_signature_method(oauth.OAuthSignatureMethod_PLAINTEXT())
|
||||
self.oauth_server.add_signature_method(oauth.OAuthSignatureMethod_HMAC_SHA1())
|
||||
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
|
||||
|
||||
# example way to send an oauth error
|
||||
def send_oauth_error(self, err=None):
|
||||
# send a 401 error
|
||||
self.send_error(401, str(err.message))
|
||||
# return the authenticate header
|
||||
header = oauth.build_authenticate_header(realm=REALM)
|
||||
for k, v in header.iteritems():
|
||||
self.send_header(k, v)
|
||||
|
||||
def do_GET(self):
|
||||
|
||||
# debug info
|
||||
#print self.command, self.path, self.headers
|
||||
|
||||
# get the post data (if any)
|
||||
postdata = None
|
||||
if self.command == 'POST':
|
||||
try:
|
||||
length = int(self.headers.getheader('content-length'))
|
||||
postdata = self.rfile.read(length)
|
||||
except:
|
||||
pass
|
||||
|
||||
# construct the oauth request from the request parameters
|
||||
oauth_request = oauth.OAuthRequest.from_request(self.command, self.path, headers=self.headers, query_string=postdata)
|
||||
|
||||
# request token
|
||||
if self.path.startswith(REQUEST_TOKEN_URL):
|
||||
try:
|
||||
# create a request token
|
||||
token = self.oauth_server.fetch_request_token(oauth_request)
|
||||
# send okay response
|
||||
self.send_response(200, 'OK')
|
||||
self.end_headers()
|
||||
# return the token
|
||||
self.wfile.write(token.to_string())
|
||||
except oauth.OAuthError, err:
|
||||
self.send_oauth_error(err)
|
||||
return
|
||||
|
||||
# user authorization
|
||||
if self.path.startswith(AUTHORIZATION_URL):
|
||||
try:
|
||||
# get the request token
|
||||
token = self.oauth_server.fetch_request_token(oauth_request)
|
||||
# authorize the token (kind of does nothing for now)
|
||||
token = self.oauth_server.authorize_token(token, None)
|
||||
token.set_verifier(VERIFIER)
|
||||
# send okay response
|
||||
self.send_response(200, 'OK')
|
||||
self.end_headers()
|
||||
# return the callback url (to show server has it)
|
||||
self.wfile.write(token.get_callback_url())
|
||||
except oauth.OAuthError, err:
|
||||
self.send_oauth_error(err)
|
||||
return
|
||||
|
||||
# access token
|
||||
if self.path.startswith(ACCESS_TOKEN_URL):
|
||||
try:
|
||||
# create an access token
|
||||
token = self.oauth_server.fetch_access_token(oauth_request)
|
||||
# send okay response
|
||||
self.send_response(200, 'OK')
|
||||
self.end_headers()
|
||||
# return the token
|
||||
self.wfile.write(token.to_string())
|
||||
except oauth.OAuthError, err:
|
||||
self.send_oauth_error(err)
|
||||
return
|
||||
|
||||
# protected resources
|
||||
if self.path.startswith(RESOURCE_URL):
|
||||
try:
|
||||
# verify the request has been oauth authorized
|
||||
consumer, token, params = self.oauth_server.verify_request(oauth_request)
|
||||
# send okay response
|
||||
self.send_response(200, 'OK')
|
||||
self.end_headers()
|
||||
# return the extra parameters - just for something to return
|
||||
self.wfile.write(str(params))
|
||||
except oauth.OAuthError, err:
|
||||
self.send_oauth_error(err)
|
||||
return
|
||||
|
||||
def do_POST(self):
|
||||
return self.do_GET()
|
||||
|
||||
def main():
|
||||
try:
|
||||
server = HTTPServer(('', 8080), RequestHandler)
|
||||
print 'Test server running...'
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
server.socket.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
655
server/oauth/oauth.py
Normal file
655
server/oauth/oauth.py
Normal file
@@ -0,0 +1,655 @@
|
||||
"""
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2007 Leah Culver
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import cgi
|
||||
import urllib
|
||||
import time
|
||||
import random
|
||||
import urlparse
|
||||
import hmac
|
||||
import binascii
|
||||
|
||||
|
||||
VERSION = '1.0' # Hi Blaine!
|
||||
HTTP_METHOD = 'GET'
|
||||
SIGNATURE_METHOD = 'PLAINTEXT'
|
||||
|
||||
|
||||
class OAuthError(RuntimeError):
|
||||
"""Generic exception class."""
|
||||
def __init__(self, message='OAuth error occured.'):
|
||||
self.message = message
|
||||
|
||||
def build_authenticate_header(realm=''):
|
||||
"""Optional WWW-Authenticate header (401 error)"""
|
||||
return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
|
||||
|
||||
def escape(s):
|
||||
"""Escape a URL including any /."""
|
||||
return urllib.quote(s, safe='~')
|
||||
|
||||
def _utf8_str(s):
|
||||
"""Convert unicode to utf-8."""
|
||||
if isinstance(s, unicode):
|
||||
return s.encode("utf-8")
|
||||
else:
|
||||
return str(s)
|
||||
|
||||
def generate_timestamp():
|
||||
"""Get seconds since epoch (UTC)."""
|
||||
return int(time.time())
|
||||
|
||||
def generate_nonce(length=8):
|
||||
"""Generate pseudorandom number."""
|
||||
return ''.join([str(random.randint(0, 9)) for i in range(length)])
|
||||
|
||||
def generate_verifier(length=8):
|
||||
"""Generate pseudorandom number."""
|
||||
return ''.join([str(random.randint(0, 9)) for i in range(length)])
|
||||
|
||||
|
||||
class OAuthConsumer(object):
|
||||
"""Consumer of OAuth authentication.
|
||||
|
||||
OAuthConsumer is a data type that represents the identity of the Consumer
|
||||
via its shared secret with the Service Provider.
|
||||
|
||||
"""
|
||||
key = None
|
||||
secret = None
|
||||
|
||||
def __init__(self, key, secret):
|
||||
self.key = key
|
||||
self.secret = secret
|
||||
|
||||
|
||||
class OAuthToken(object):
|
||||
"""OAuthToken is a data type that represents an End User via either an access
|
||||
or request token.
|
||||
|
||||
key -- the token
|
||||
secret -- the token secret
|
||||
|
||||
"""
|
||||
key = None
|
||||
secret = None
|
||||
callback = None
|
||||
callback_confirmed = None
|
||||
verifier = None
|
||||
|
||||
def __init__(self, key, secret):
|
||||
self.key = key
|
||||
self.secret = secret
|
||||
|
||||
def set_callback(self, callback):
|
||||
self.callback = callback
|
||||
self.callback_confirmed = 'true'
|
||||
|
||||
def set_verifier(self, verifier=None):
|
||||
if verifier is not None:
|
||||
self.verifier = verifier
|
||||
else:
|
||||
self.verifier = generate_verifier()
|
||||
|
||||
def get_callback_url(self):
|
||||
if self.callback and self.verifier:
|
||||
# Append the oauth_verifier.
|
||||
parts = urlparse.urlparse(self.callback)
|
||||
scheme, netloc, path, params, query, fragment = parts[:6]
|
||||
if query:
|
||||
query = '%s&oauth_verifier=%s' % (query, self.verifier)
|
||||
else:
|
||||
query = 'oauth_verifier=%s' % self.verifier
|
||||
return urlparse.urlunparse((scheme, netloc, path, params,
|
||||
query, fragment))
|
||||
return self.callback
|
||||
|
||||
def to_string(self):
|
||||
data = {
|
||||
'oauth_token': self.key,
|
||||
'oauth_token_secret': self.secret,
|
||||
}
|
||||
if self.callback_confirmed is not None:
|
||||
data['oauth_callback_confirmed'] = self.callback_confirmed
|
||||
return urllib.urlencode(data)
|
||||
|
||||
def from_string(s):
|
||||
""" Returns a token from something like:
|
||||
oauth_token_secret=xxx&oauth_token=xxx
|
||||
"""
|
||||
params = cgi.parse_qs(s, keep_blank_values=False)
|
||||
key = params['oauth_token'][0]
|
||||
secret = params['oauth_token_secret'][0]
|
||||
token = OAuthToken(key, secret)
|
||||
try:
|
||||
token.callback_confirmed = params['oauth_callback_confirmed'][0]
|
||||
except KeyError:
|
||||
pass # 1.0, no callback confirmed.
|
||||
return token
|
||||
from_string = staticmethod(from_string)
|
||||
|
||||
def __str__(self):
|
||||
return self.to_string()
|
||||
|
||||
|
||||
class OAuthRequest(object):
|
||||
"""OAuthRequest represents the request and can be serialized.
|
||||
|
||||
OAuth parameters:
|
||||
- oauth_consumer_key
|
||||
- oauth_token
|
||||
- oauth_signature_method
|
||||
- oauth_signature
|
||||
- oauth_timestamp
|
||||
- oauth_nonce
|
||||
- oauth_version
|
||||
- oauth_verifier
|
||||
... any additional parameters, as defined by the Service Provider.
|
||||
"""
|
||||
parameters = None # OAuth parameters.
|
||||
http_method = HTTP_METHOD
|
||||
http_url = None
|
||||
version = VERSION
|
||||
|
||||
def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
|
||||
self.http_method = http_method
|
||||
self.http_url = http_url
|
||||
self.parameters = parameters or {}
|
||||
|
||||
def set_parameter(self, parameter, value):
|
||||
self.parameters[parameter] = value
|
||||
|
||||
def get_parameter(self, parameter):
|
||||
try:
|
||||
return self.parameters[parameter]
|
||||
except:
|
||||
raise OAuthError('Parameter not found: %s' % parameter)
|
||||
|
||||
def _get_timestamp_nonce(self):
|
||||
return self.get_parameter('oauth_timestamp'), self.get_parameter(
|
||||
'oauth_nonce')
|
||||
|
||||
def get_nonoauth_parameters(self):
|
||||
"""Get any non-OAuth parameters."""
|
||||
parameters = {}
|
||||
for k, v in self.parameters.iteritems():
|
||||
# Ignore oauth parameters.
|
||||
if k.find('oauth_') < 0:
|
||||
parameters[k] = v
|
||||
return parameters
|
||||
|
||||
def to_header(self, realm=''):
|
||||
"""Serialize as a header for an HTTPAuth request."""
|
||||
auth_header = 'OAuth realm="%s"' % realm
|
||||
# Add the oauth parameters.
|
||||
if self.parameters:
|
||||
for k, v in self.parameters.iteritems():
|
||||
if k[:6] == 'oauth_':
|
||||
auth_header += ', %s="%s"' % (k, escape(str(v)))
|
||||
return {'Authorization': auth_header}
|
||||
|
||||
def to_postdata(self):
|
||||
"""Serialize as post data for a POST request."""
|
||||
return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) \
|
||||
for k, v in self.parameters.iteritems()])
|
||||
|
||||
def to_url(self):
|
||||
"""Serialize as a URL for a GET request."""
|
||||
return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata())
|
||||
|
||||
def get_normalized_parameters(self):
|
||||
"""Return a string that contains the parameters that must be signed."""
|
||||
params = self.parameters
|
||||
try:
|
||||
# Exclude the signature if it exists.
|
||||
del params['oauth_signature']
|
||||
except:
|
||||
pass
|
||||
# Escape key values before sorting.
|
||||
key_values = [(escape(_utf8_str(k)), escape(_utf8_str(v))) \
|
||||
for k,v in params.items()]
|
||||
# Sort lexicographically, first after key, then after value.
|
||||
key_values.sort()
|
||||
# Combine key value pairs into a string.
|
||||
return '&'.join(['%s=%s' % (k, v) for k, v in key_values])
|
||||
|
||||
def get_normalized_http_method(self):
|
||||
"""Uppercases the http method."""
|
||||
return self.http_method.upper()
|
||||
|
||||
def get_normalized_http_url(self):
|
||||
"""Parses the URL and rebuilds it to be scheme://host/path."""
|
||||
parts = urlparse.urlparse(self.http_url)
|
||||
scheme, netloc, path = parts[:3]
|
||||
# Exclude default port numbers.
|
||||
if scheme == 'http' and netloc[-3:] == ':80':
|
||||
netloc = netloc[:-3]
|
||||
elif scheme == 'https' and netloc[-4:] == ':443':
|
||||
netloc = netloc[:-4]
|
||||
return '%s://%s%s' % (scheme, netloc, path)
|
||||
|
||||
def sign_request(self, signature_method, consumer, token):
|
||||
"""Set the signature parameter to the result of build_signature."""
|
||||
# Set the signature method.
|
||||
self.set_parameter('oauth_signature_method',
|
||||
signature_method.get_name())
|
||||
# Set the signature.
|
||||
self.set_parameter('oauth_signature',
|
||||
self.build_signature(signature_method, consumer, token))
|
||||
|
||||
def build_signature(self, signature_method, consumer, token):
|
||||
"""Calls the build signature method within the signature method."""
|
||||
return signature_method.build_signature(self, consumer, token)
|
||||
|
||||
def from_request(http_method, http_url, headers=None, parameters=None,
|
||||
query_string=None):
|
||||
"""Combines multiple parameter sources."""
|
||||
if parameters is None:
|
||||
parameters = {}
|
||||
|
||||
# Headers
|
||||
if headers and 'Authorization' in headers:
|
||||
auth_header = headers['Authorization']
|
||||
# Check that the authorization header is OAuth.
|
||||
if auth_header[:6] == 'OAuth ':
|
||||
auth_header = auth_header[6:]
|
||||
try:
|
||||
# Get the parameters from the header.
|
||||
header_params = OAuthRequest._split_header(auth_header)
|
||||
parameters.update(header_params)
|
||||
except:
|
||||
raise OAuthError('Unable to parse OAuth parameters from '
|
||||
'Authorization header.')
|
||||
|
||||
# GET or POST query string.
|
||||
if query_string:
|
||||
query_params = OAuthRequest._split_url_string(query_string)
|
||||
parameters.update(query_params)
|
||||
|
||||
# URL parameters.
|
||||
param_str = urlparse.urlparse(http_url)[4] # query
|
||||
url_params = OAuthRequest._split_url_string(param_str)
|
||||
parameters.update(url_params)
|
||||
|
||||
if parameters:
|
||||
return OAuthRequest(http_method, http_url, parameters)
|
||||
|
||||
return None
|
||||
from_request = staticmethod(from_request)
|
||||
|
||||
def from_consumer_and_token(oauth_consumer, token=None,
|
||||
callback=None, verifier=None, http_method=HTTP_METHOD,
|
||||
http_url=None, parameters=None):
|
||||
if not parameters:
|
||||
parameters = {}
|
||||
|
||||
defaults = {
|
||||
'oauth_consumer_key': oauth_consumer.key,
|
||||
'oauth_timestamp': generate_timestamp(),
|
||||
'oauth_nonce': generate_nonce(),
|
||||
'oauth_version': OAuthRequest.version,
|
||||
}
|
||||
|
||||
defaults.update(parameters)
|
||||
parameters = defaults
|
||||
|
||||
if token:
|
||||
parameters['oauth_token'] = token.key
|
||||
if token.callback:
|
||||
parameters['oauth_callback'] = token.callback
|
||||
# 1.0a support for verifier.
|
||||
if verifier:
|
||||
parameters['oauth_verifier'] = verifier
|
||||
elif callback:
|
||||
# 1.0a support for callback in the request token request.
|
||||
parameters['oauth_callback'] = callback
|
||||
|
||||
return OAuthRequest(http_method, http_url, parameters)
|
||||
from_consumer_and_token = staticmethod(from_consumer_and_token)
|
||||
|
||||
def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD,
|
||||
http_url=None, parameters=None):
|
||||
if not parameters:
|
||||
parameters = {}
|
||||
|
||||
parameters['oauth_token'] = token.key
|
||||
|
||||
if callback:
|
||||
parameters['oauth_callback'] = callback
|
||||
|
||||
return OAuthRequest(http_method, http_url, parameters)
|
||||
from_token_and_callback = staticmethod(from_token_and_callback)
|
||||
|
||||
def _split_header(header):
|
||||
"""Turn Authorization: header into parameters."""
|
||||
params = {}
|
||||
parts = header.split(',')
|
||||
for param in parts:
|
||||
# Ignore realm parameter.
|
||||
if param.find('realm') > -1:
|
||||
continue
|
||||
# Remove whitespace.
|
||||
param = param.strip()
|
||||
# Split key-value.
|
||||
param_parts = param.split('=', 1)
|
||||
# Remove quotes and unescape the value.
|
||||
params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
|
||||
return params
|
||||
_split_header = staticmethod(_split_header)
|
||||
|
||||
def _split_url_string(param_str):
|
||||
"""Turn URL string into parameters."""
|
||||
parameters = cgi.parse_qs(param_str, keep_blank_values=False)
|
||||
for k, v in parameters.iteritems():
|
||||
parameters[k] = urllib.unquote(v[0])
|
||||
return parameters
|
||||
_split_url_string = staticmethod(_split_url_string)
|
||||
|
||||
class OAuthServer(object):
|
||||
"""A worker to check the validity of a request against a data store."""
|
||||
timestamp_threshold = 300 # In seconds, five minutes.
|
||||
version = VERSION
|
||||
signature_methods = None
|
||||
data_store = None
|
||||
|
||||
def __init__(self, data_store=None, signature_methods=None):
|
||||
self.data_store = data_store
|
||||
self.signature_methods = signature_methods or {}
|
||||
|
||||
def set_data_store(self, data_store):
|
||||
self.data_store = data_store
|
||||
|
||||
def get_data_store(self):
|
||||
return self.data_store
|
||||
|
||||
def add_signature_method(self, signature_method):
|
||||
self.signature_methods[signature_method.get_name()] = signature_method
|
||||
return self.signature_methods
|
||||
|
||||
def fetch_request_token(self, oauth_request):
|
||||
"""Processes a request_token request and returns the
|
||||
request token on success.
|
||||
"""
|
||||
try:
|
||||
# Get the request token for authorization.
|
||||
token = self._get_token(oauth_request, 'request')
|
||||
except OAuthError:
|
||||
# No token required for the initial token request.
|
||||
version = self._get_version(oauth_request)
|
||||
consumer = self._get_consumer(oauth_request)
|
||||
try:
|
||||
callback = self.get_callback(oauth_request)
|
||||
except OAuthError:
|
||||
callback = None # 1.0, no callback specified.
|
||||
self._check_signature(oauth_request, consumer, None)
|
||||
# Fetch a new token.
|
||||
token = self.data_store.fetch_request_token(consumer, callback)
|
||||
return token
|
||||
|
||||
def fetch_access_token(self, oauth_request):
|
||||
"""Processes an access_token request and returns the
|
||||
access token on success.
|
||||
"""
|
||||
version = self._get_version(oauth_request)
|
||||
consumer = self._get_consumer(oauth_request)
|
||||
try:
|
||||
verifier = self._get_verifier(oauth_request)
|
||||
except OAuthError:
|
||||
verifier = None
|
||||
# Get the request token.
|
||||
token = self._get_token(oauth_request, 'request')
|
||||
self._check_signature(oauth_request, consumer, token)
|
||||
new_token = self.data_store.fetch_access_token(consumer, token, verifier)
|
||||
return new_token
|
||||
|
||||
def verify_request(self, oauth_request):
|
||||
"""Verifies an api call and checks all the parameters."""
|
||||
# -> consumer and token
|
||||
version = self._get_version(oauth_request)
|
||||
consumer = self._get_consumer(oauth_request)
|
||||
# Get the access token.
|
||||
token = self._get_token(oauth_request, 'access')
|
||||
self._check_signature(oauth_request, consumer, token)
|
||||
parameters = oauth_request.get_nonoauth_parameters()
|
||||
return consumer, token, parameters
|
||||
|
||||
def authorize_token(self, token, user):
|
||||
"""Authorize a request token."""
|
||||
return self.data_store.authorize_request_token(token, user)
|
||||
|
||||
def get_callback(self, oauth_request):
|
||||
"""Get the callback URL."""
|
||||
return oauth_request.get_parameter('oauth_callback')
|
||||
|
||||
def build_authenticate_header(self, realm=''):
|
||||
"""Optional support for the authenticate header."""
|
||||
return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
|
||||
|
||||
def _get_version(self, oauth_request):
|
||||
"""Verify the correct version request for this server."""
|
||||
try:
|
||||
version = oauth_request.get_parameter('oauth_version')
|
||||
except:
|
||||
version = VERSION
|
||||
if version and version != self.version:
|
||||
raise OAuthError('OAuth version %s not supported.' % str(version))
|
||||
return version
|
||||
|
||||
def _get_signature_method(self, oauth_request):
|
||||
"""Figure out the signature with some defaults."""
|
||||
try:
|
||||
signature_method = oauth_request.get_parameter(
|
||||
'oauth_signature_method')
|
||||
except:
|
||||
signature_method = SIGNATURE_METHOD
|
||||
try:
|
||||
# Get the signature method object.
|
||||
signature_method = self.signature_methods[signature_method]
|
||||
except:
|
||||
signature_method_names = ', '.join(self.signature_methods.keys())
|
||||
raise OAuthError('Signature method %s not supported try one of the '
|
||||
'following: %s' % (signature_method, signature_method_names))
|
||||
|
||||
return signature_method
|
||||
|
||||
def _get_consumer(self, oauth_request):
|
||||
consumer_key = oauth_request.get_parameter('oauth_consumer_key')
|
||||
consumer = self.data_store.lookup_consumer(consumer_key)
|
||||
if not consumer:
|
||||
raise OAuthError('Invalid consumer.')
|
||||
return consumer
|
||||
|
||||
def _get_token(self, oauth_request, token_type='access'):
|
||||
"""Try to find the token for the provided request token key."""
|
||||
token_field = oauth_request.get_parameter('oauth_token')
|
||||
token = self.data_store.lookup_token(token_type, token_field)
|
||||
if not token:
|
||||
raise OAuthError('Invalid %s token: %s' % (token_type, token_field))
|
||||
return token
|
||||
|
||||
def _get_verifier(self, oauth_request):
|
||||
return oauth_request.get_parameter('oauth_verifier')
|
||||
|
||||
def _check_signature(self, oauth_request, consumer, token):
|
||||
timestamp, nonce = oauth_request._get_timestamp_nonce()
|
||||
self._check_timestamp(timestamp)
|
||||
self._check_nonce(consumer, token, nonce)
|
||||
signature_method = self._get_signature_method(oauth_request)
|
||||
try:
|
||||
signature = oauth_request.get_parameter('oauth_signature')
|
||||
except:
|
||||
raise OAuthError('Missing signature.')
|
||||
# Validate the signature.
|
||||
valid_sig = signature_method.check_signature(oauth_request, consumer,
|
||||
token, signature)
|
||||
if not valid_sig:
|
||||
key, base = signature_method.build_signature_base_string(
|
||||
oauth_request, consumer, token)
|
||||
raise OAuthError('Invalid signature. Expected signature base '
|
||||
'string: %s' % base)
|
||||
built = signature_method.build_signature(oauth_request, consumer, token)
|
||||
|
||||
def _check_timestamp(self, timestamp):
|
||||
"""Verify that timestamp is recentish."""
|
||||
timestamp = int(timestamp)
|
||||
now = int(time.time())
|
||||
lapsed = abs(now - timestamp)
|
||||
if lapsed > self.timestamp_threshold:
|
||||
raise OAuthError('Expired timestamp: given %d and now %s has a '
|
||||
'greater difference than threshold %d' %
|
||||
(timestamp, now, self.timestamp_threshold))
|
||||
|
||||
def _check_nonce(self, consumer, token, nonce):
|
||||
"""Verify that the nonce is uniqueish."""
|
||||
nonce = self.data_store.lookup_nonce(consumer, token, nonce)
|
||||
if nonce:
|
||||
raise OAuthError('Nonce already used: %s' % str(nonce))
|
||||
|
||||
|
||||
class OAuthClient(object):
|
||||
"""OAuthClient is a worker to attempt to execute a request."""
|
||||
consumer = None
|
||||
token = None
|
||||
|
||||
def __init__(self, oauth_consumer, oauth_token):
|
||||
self.consumer = oauth_consumer
|
||||
self.token = oauth_token
|
||||
|
||||
def get_consumer(self):
|
||||
return self.consumer
|
||||
|
||||
def get_token(self):
|
||||
return self.token
|
||||
|
||||
def fetch_request_token(self, oauth_request):
|
||||
"""-> OAuthToken."""
|
||||
raise NotImplementedError
|
||||
|
||||
def fetch_access_token(self, oauth_request):
|
||||
"""-> OAuthToken."""
|
||||
raise NotImplementedError
|
||||
|
||||
def access_resource(self, oauth_request):
|
||||
"""-> Some protected resource."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class OAuthDataStore(object):
|
||||
"""A database abstraction used to lookup consumers and tokens."""
|
||||
|
||||
def lookup_consumer(self, key):
|
||||
"""-> OAuthConsumer."""
|
||||
raise NotImplementedError
|
||||
|
||||
def lookup_token(self, oauth_consumer, token_type, token_token):
|
||||
"""-> OAuthToken."""
|
||||
raise NotImplementedError
|
||||
|
||||
def lookup_nonce(self, oauth_consumer, oauth_token, nonce):
|
||||
"""-> OAuthToken."""
|
||||
raise NotImplementedError
|
||||
|
||||
def fetch_request_token(self, oauth_consumer, oauth_callback):
|
||||
"""-> OAuthToken."""
|
||||
raise NotImplementedError
|
||||
|
||||
def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier):
|
||||
"""-> OAuthToken."""
|
||||
raise NotImplementedError
|
||||
|
||||
def authorize_request_token(self, oauth_token, user):
|
||||
"""-> OAuthToken."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class OAuthSignatureMethod(object):
|
||||
"""A strategy class that implements a signature method."""
|
||||
def get_name(self):
|
||||
"""-> str."""
|
||||
raise NotImplementedError
|
||||
|
||||
def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token):
|
||||
"""-> str key, str raw."""
|
||||
raise NotImplementedError
|
||||
|
||||
def build_signature(self, oauth_request, oauth_consumer, oauth_token):
|
||||
"""-> str."""
|
||||
raise NotImplementedError
|
||||
|
||||
def check_signature(self, oauth_request, consumer, token, signature):
|
||||
built = self.build_signature(oauth_request, consumer, token)
|
||||
return built == signature
|
||||
|
||||
|
||||
class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
|
||||
|
||||
def get_name(self):
|
||||
return 'HMAC-SHA1'
|
||||
|
||||
def build_signature_base_string(self, oauth_request, consumer, token):
|
||||
sig = (
|
||||
escape(oauth_request.get_normalized_http_method()),
|
||||
escape(oauth_request.get_normalized_http_url()),
|
||||
escape(oauth_request.get_normalized_parameters()),
|
||||
)
|
||||
|
||||
key = '%s&' % escape(consumer.secret)
|
||||
if token:
|
||||
key += escape(token.secret)
|
||||
raw = '&'.join(sig)
|
||||
return key, raw
|
||||
|
||||
def build_signature(self, oauth_request, consumer, token):
|
||||
"""Builds the base signature string."""
|
||||
key, raw = self.build_signature_base_string(oauth_request, consumer,
|
||||
token)
|
||||
|
||||
# HMAC object.
|
||||
try:
|
||||
import hashlib # 2.5
|
||||
hashed = hmac.new(key, raw, hashlib.sha1)
|
||||
except:
|
||||
import sha # Deprecated
|
||||
hashed = hmac.new(key, raw, sha)
|
||||
|
||||
# Calculate the digest base 64.
|
||||
return binascii.b2a_base64(hashed.digest())[:-1]
|
||||
|
||||
|
||||
class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
|
||||
|
||||
def get_name(self):
|
||||
return 'PLAINTEXT'
|
||||
|
||||
def build_signature_base_string(self, oauth_request, consumer, token):
|
||||
"""Concatenates the consumer key and secret."""
|
||||
sig = '%s&' % escape(consumer.secret)
|
||||
if token:
|
||||
sig = sig + escape(token.secret)
|
||||
return sig, sig
|
||||
|
||||
def build_signature(self, oauth_request, consumer, token):
|
||||
key, raw = self.build_signature_base_string(oauth_request, consumer,
|
||||
token)
|
||||
return key
|
||||
Reference in New Issue
Block a user