Merge pull request #57 from michele-comitini/master
OAuth 2.0 rfc6749 compliance
This commit is contained in:
@@ -19,6 +19,7 @@ from urllib2 import urlopen
|
|||||||
import urllib2
|
import urllib2
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
|
|
||||||
|
from gluon import current
|
||||||
|
|
||||||
class OAuthAccount(object):
|
class OAuthAccount(object):
|
||||||
"""
|
"""
|
||||||
@@ -117,8 +118,8 @@ class OAuthAccount(object):
|
|||||||
self.client_id = client_id
|
self.client_id = client_id
|
||||||
self.client_secret = client_secret
|
self.client_secret = client_secret
|
||||||
self.code = None
|
self.code = None
|
||||||
self.request = g['request']
|
self.request = current.request
|
||||||
self.session = g['session']
|
self.session = current.session
|
||||||
self.auth_url = auth_url
|
self.auth_url = auth_url
|
||||||
self.token_url = token_url
|
self.token_url = token_url
|
||||||
self.access_token_url = access_token_url
|
self.access_token_url = access_token_url
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ License: LGPL v3
|
|||||||
|
|
||||||
Adds support for OAuth 2.0 authentication to web2py.
|
Adds support for OAuth 2.0 authentication to web2py.
|
||||||
|
|
||||||
OAuth 2.0 Draft: http://tools.ietf.org/html/draft-ietf-oauth-v2-10
|
OAuth 2.0 spec: http://tools.ietf.org/html/rfc6749
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
@@ -17,6 +18,7 @@ import urllib2
|
|||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
from gluon import current, redirect, HTTP
|
from gluon import current, redirect, HTTP
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
class OAuthAccount(object):
|
class OAuthAccount(object):
|
||||||
"""
|
"""
|
||||||
@@ -105,7 +107,10 @@ server for requests. It can be used for the optional"scope" parameters for Face
|
|||||||
if not http_host:
|
if not http_host:
|
||||||
http_host = r.env.http_host
|
http_host = r.env.http_host
|
||||||
|
|
||||||
url_scheme = r.env.wsgi_url_scheme
|
if r.env.https == 'on':
|
||||||
|
url_scheme = 'https'
|
||||||
|
else:
|
||||||
|
url_scheme = r.env.wsgi_url_scheme
|
||||||
if next:
|
if next:
|
||||||
path_info = next
|
path_info = next
|
||||||
else:
|
else:
|
||||||
@@ -144,31 +149,45 @@ server for requests. It can be used for the optional"scope" parameters for Face
|
|||||||
# reuse token until expiration
|
# reuse token until expiration
|
||||||
if expires == 0 or expires > time.time():
|
if expires == 0 or expires > time.time():
|
||||||
return current.session.token['access_token']
|
return current.session.token['access_token']
|
||||||
if current.session.code:
|
|
||||||
|
code = current.request.vars.code
|
||||||
|
|
||||||
|
if code:
|
||||||
data = dict(client_id=self.client_id,
|
data = dict(client_id=self.client_id,
|
||||||
client_secret=self.client_secret,
|
client_secret=self.client_secret,
|
||||||
redirect_uri=current.session.redirect_uri,
|
redirect_uri=current.session.redirect_uri,
|
||||||
response_type='token', code=current.session.code)
|
code=code,
|
||||||
|
grant_type='authorization_code'
|
||||||
|
)
|
||||||
|
|
||||||
if self.args:
|
|
||||||
data.update(self.args)
|
|
||||||
open_url = None
|
open_url = None
|
||||||
opener = self.__build_url_opener(self.token_url)
|
opener = self.__build_url_opener(self.token_url)
|
||||||
try:
|
try:
|
||||||
open_url = opener.open(self.token_url, urlencode(data))
|
open_url = opener.open(self.token_url, urlencode(data))
|
||||||
except urllib2.HTTPError, e:
|
except urllib2.HTTPError, e:
|
||||||
tmp = e.read()
|
tmp = e.read()
|
||||||
print tmp
|
|
||||||
raise Exception(tmp)
|
raise Exception(tmp)
|
||||||
finally:
|
finally:
|
||||||
del current.session.code # throw it away
|
if current.session.code:
|
||||||
|
del current.session.code # throw it away
|
||||||
|
|
||||||
if open_url:
|
if open_url:
|
||||||
try:
|
try:
|
||||||
data = open_url.read()
|
data = open_url.read()
|
||||||
tokendata = cgi.parse_qs(data)
|
resp_type = open_url.info().get('Content-Type')
|
||||||
current.session.token = \
|
# try json style first
|
||||||
dict([(k, v[-1]) for k, v in tokendata.items()])
|
if not resp_type or resp_type == 'application/json':
|
||||||
|
try:
|
||||||
|
tokendata = json.loads(data)
|
||||||
|
current.session.token = tokendata
|
||||||
|
except Exception, e:
|
||||||
|
raise Exception("Cannot parse oauth server response %s %s" % (data, e))
|
||||||
|
else: # try facebook style first with x-www-form-encoded
|
||||||
|
tokendata = cgi.parse_qs(data)
|
||||||
|
current.session.token = \
|
||||||
|
dict([(k, v[-1]) for k, v in tokendata.items()])
|
||||||
|
if not tokendata: # parsing failed?
|
||||||
|
raise Exception("Cannot parse oauth server response %s" % data)
|
||||||
# set expiration absolute time try to avoid broken
|
# set expiration absolute time try to avoid broken
|
||||||
# implementations where "expires_in" becomes "expires"
|
# implementations where "expires_in" becomes "expires"
|
||||||
if 'expires_in' in current.session.token:
|
if 'expires_in' in current.session.token:
|
||||||
@@ -258,20 +277,16 @@ server for requests. It can be used for the optional"scope" parameters for Face
|
|||||||
accessToken()
|
accessToken()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.accessToken():
|
token = self.accessToken()
|
||||||
if not current.request.vars.code:
|
if not token:
|
||||||
current.session.redirect_uri = self.__redirect_uri(next)
|
current.session.redirect_uri = self.__redirect_uri(next)
|
||||||
data = dict(redirect_uri=current.session.redirect_uri,
|
data = dict(redirect_uri=current.session.redirect_uri,
|
||||||
response_type='code',
|
response_type='code',
|
||||||
client_id=self.client_id)
|
client_id=self.client_id)
|
||||||
if self.args:
|
if self.args:
|
||||||
data.update(self.args)
|
data.update(self.args)
|
||||||
auth_request_url = self.auth_url + "?" + urlencode(data)
|
auth_request_url = self.auth_url + "?" + urlencode(data)
|
||||||
raise HTTP(307,
|
raise HTTP(307,
|
||||||
"You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>",
|
"You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>",
|
||||||
Location=auth_request_url)
|
Location=auth_request_url)
|
||||||
else:
|
return
|
||||||
current.session.code = current.request.vars.code
|
|
||||||
self.accessToken()
|
|
||||||
return current.session.code
|
|
||||||
return None
|
|
||||||
|
|||||||
+3
-3
@@ -4434,12 +4434,12 @@ class Service(object):
|
|||||||
return return_error(id, e.code, e.info)
|
return return_error(id, e.code, e.info)
|
||||||
except BaseException:
|
except BaseException:
|
||||||
etype, eval, etb = sys.exc_info()
|
etype, eval, etb = sys.exc_info()
|
||||||
code = -32001
|
code = -32099
|
||||||
data = '%s: %s\n' % (etype.__name__, eval) + request.is_local and traceback.format_tb(etb)
|
data = '%s: %s\n' % (etype.__name__, eval) + str(request.is_local and traceback.format_tb(etb))
|
||||||
return return_error(id, code, data=data)
|
return return_error(id, code, data=data)
|
||||||
except:
|
except:
|
||||||
etype, eval, etb = sys.exc_info()
|
etype, eval, etb = sys.exc_info()
|
||||||
return return_error(id, 32099, data='Exception %s: %s' % (etype, eval))
|
return return_error(id, -32099, data='Exception %s: %s' % (etype, eval))
|
||||||
|
|
||||||
|
|
||||||
def serve_xmlrpc(self):
|
def serve_xmlrpc(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user