add openid+oauth support, google almost working (oauth not returning due to dns issue)
This commit is contained in:
Родитель
7fb9138354
Коммит
ed3d6b26bf
|
@ -18,6 +18,9 @@ oauth.twitter.com.consumer_secret = prh6A961516mJ3XEjd7eERsGxuVZqycrBB6lV7LQ
|
|||
oauth.facebook.com.app_id = 158102624846
|
||||
oauth.facebook.com.app_secret = 4203f7f23803f405e06509ec4d4b9729
|
||||
|
||||
oauth.google.com.consumer_key = anonymous
|
||||
oauth.google.com.consumer_secret = anonymous
|
||||
|
||||
|
||||
[composite:main]
|
||||
use = egg:Paste#urlmap
|
||||
|
|
|
@ -74,17 +74,16 @@ the contacts API that uses @me/@self.
|
|||
service = get_provider(provider)
|
||||
|
||||
auth = service.responder()
|
||||
access_key = auth.verify()
|
||||
data = auth.get_credentials(access_key)
|
||||
#import sys; print >> sys.stderr, data
|
||||
user = auth.verify()
|
||||
import sys; print >> sys.stderr, user
|
||||
|
||||
account = data['profile']['accounts'][0]
|
||||
account = user['profile']['accounts'][0]
|
||||
|
||||
acct = self._get_or_create_account(provider, account['userid'], account['username'])
|
||||
acct.profile = data['profile']
|
||||
acct.oauth_token = data['oauth_token']
|
||||
if 'oauth_token_secret' in data:
|
||||
acct.oauth_token_secret = data['oauth_token_secret']
|
||||
acct.profile = user['profile']
|
||||
acct.oauth_token = user.get('oauth_token', None)
|
||||
if 'oauth_token_secret' in user:
|
||||
acct.oauth_token_secret = user['oauth_token_secret']
|
||||
Session.commit()
|
||||
|
||||
fragment = "oauth_success_" + provider
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from linkdrop.lib.oauth import facebook_
|
||||
#from linkdrop.lib.oauth.google_ import GoogleResponder
|
||||
from linkdrop.lib.oauth import google_
|
||||
#from linkdrop.lib.oauth.live_ import LiveResponder
|
||||
#from linkdrop.lib.oauth.openidconsumer import OpenIDResponder
|
||||
from linkdrop.lib.oauth import twitter_
|
||||
|
@ -10,7 +10,8 @@ __all__ = ['get_provider']
|
|||
# XXX need a better way to do this
|
||||
_providers = {
|
||||
twitter_.domain: twitter_,
|
||||
facebook_.domain: facebook_
|
||||
facebook_.domain: facebook_,
|
||||
google_.domain: google_
|
||||
}
|
||||
|
||||
def get_provider(provider):
|
||||
|
|
|
@ -78,9 +78,9 @@ class OAuth1():
|
|||
redirect(session['end_point_auth_failure'])
|
||||
|
||||
access_token = dict(urlparse.parse_qsl(content))
|
||||
return access_token
|
||||
return self._get_credentials(access_token)
|
||||
|
||||
def get_credentials(self, access_token):
|
||||
def _get_credentials(self, access_token):
|
||||
return access_token
|
||||
|
||||
class OAuth2():
|
||||
|
@ -109,7 +109,7 @@ class OAuth2():
|
|||
def verify(self):
|
||||
code = request.GET.get('code')
|
||||
if not code:
|
||||
redirect(session['end_point_auth_failure'])
|
||||
raise Exception("No oauth code received")
|
||||
|
||||
return_to = url(controller='account', action="verify",
|
||||
qualified=True)
|
||||
|
@ -124,7 +124,7 @@ class OAuth2():
|
|||
raise Exception("Error status: %r", resp['status'])
|
||||
|
||||
access_token = parse_qs(content)['access_token'][0]
|
||||
return access_token
|
||||
return self._get_credentials(access_token)
|
||||
|
||||
def get_credentials(self, access_token):
|
||||
def _get_credentials(self, access_token):
|
||||
return access_token
|
||||
|
|
|
@ -97,7 +97,7 @@ class responder(OAuth2):
|
|||
self.authorization_url = 'https://graph.facebook.com/oauth/authorize'
|
||||
self.access_token_url = 'https://graph.facebook.com/oauth/access_token'
|
||||
|
||||
def get_credentials(self, access_token):
|
||||
def _get_credentials(self, access_token):
|
||||
fields = 'id,first_name,last_name,name,link,birthday,email,website,verified,picture,gender,timezone'
|
||||
client = httplib2.Http()
|
||||
resp, content = client.request(url(self.profile_url, access_token=access_token, fields=fields))
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
"""Google Responder
|
||||
|
||||
A Google responder that authenticates against Google using OpenID, or optionally
|
||||
can use OpenId+OAuth hybrid protocol to request access to Google Apps using OAuth2.
|
||||
|
||||
"""
|
||||
import urlparse
|
||||
|
||||
from openid.extensions import ax
|
||||
import oauth2 as oauth
|
||||
|
||||
from pylons import config, request, response, session, tmpl_context as c, url
|
||||
from pylons.controllers.util import abort, redirect
|
||||
|
||||
from linkdrop.lib.oauth.oid_extensions import OAuthRequest
|
||||
from linkdrop.lib.oauth.oid_extensions import UIRequest
|
||||
from linkdrop.lib.oauth.openidconsumer import ax_attributes, alternate_ax_attributes, attributes
|
||||
from linkdrop.lib.oauth.openidconsumer import OpenIDResponder
|
||||
|
||||
GOOGLE_OAUTH = 'https://www.google.com/accounts/OAuthGetAccessToken'
|
||||
|
||||
domain = 'google.com'
|
||||
|
||||
class responder(OpenIDResponder):
|
||||
def __init__(self, consumer=None, oauth_key=None, oauth_secret=None, request_attributes=None, *args,
|
||||
**kwargs):
|
||||
"""Handle Google Auth
|
||||
|
||||
This also handles making an OAuth request during the OpenID
|
||||
authentication.
|
||||
|
||||
"""
|
||||
OpenIDResponder.__init__(self, domain)
|
||||
self.consumer_key = self.config.get('consumer_key')
|
||||
self.consumer_secret = self.config.get('consumer_secret')
|
||||
|
||||
def _lookup_identifier(self, identifier):
|
||||
"""Return the Google OpenID directed endpoint"""
|
||||
return "https://www.google.com/accounts/o8/id"
|
||||
|
||||
def _update_authrequest(self, authrequest):
|
||||
"""Update the authrequest with Attribute Exchange and optionally OAuth
|
||||
|
||||
To optionally request OAuth, the request POST must include an ``oauth_scope``
|
||||
parameter that indicates what Google Apps should have access requested.
|
||||
|
||||
"""
|
||||
request_attributes = request.POST.get('ax_attributes',
|
||||
['country', 'email', 'first_name', 'last_name', 'language'])
|
||||
ax_request = ax.FetchRequest()
|
||||
for attr in request_attributes:
|
||||
ax_request.add(ax.AttrInfo(attributes[attr], required=True))
|
||||
authrequest.addExtension(ax_request)
|
||||
|
||||
oauth_request = OAuthRequest(consumer=self.consumer_key, scope=request.POST.get('scope', 'http://www.google.com/m8/feeds/'))
|
||||
authrequest.addExtension(oauth_request)
|
||||
|
||||
if 'popup_mode' in request.POST:
|
||||
kw_args = {'mode': request.POST['popup_mode']}
|
||||
if 'popup_icon' in request.POST:
|
||||
kw_args['icon'] = request.POST['popup_icon']
|
||||
ui_request = UIRequest(**kw_args)
|
||||
authrequest.addExtension(ui_request)
|
||||
return None
|
||||
|
||||
def _get_access_token(self, request_token):
|
||||
"""Retrieve the access token if OAuth hybrid was used"""
|
||||
consumer = oauth.Consumer(self.consumer_key, self.consumer_secret)
|
||||
token = oauth.Token(key=request_token, secret='')
|
||||
client = oauth.Client(consumer, token)
|
||||
resp, content = client.request(GOOGLE_OAUTH, "POST")
|
||||
if resp['status'] != '200':
|
||||
return None
|
||||
|
||||
return dict(urlparse.parse_qsl(content))
|
||||
|
||||
def _get_credentials(self, result_data):
|
||||
|
||||
#{'profile': {'preferredUsername': u'mixedpuppy',
|
||||
# 'displayName': u'Shane Caraveo',
|
||||
# 'name':
|
||||
# {'givenName': u'Shane',
|
||||
# 'formatted': u'Shane Caraveo',
|
||||
# 'familyName': u'Caraveo'},
|
||||
# 'providerName': 'Google',
|
||||
# 'verifiedEmail': u'mixedpuppy@gmail.com',
|
||||
# 'identifier': 'https://www.google.com/accounts/o8/id?id=AItOawnEHbJcEY5EtwX7vf81_x2P4KUjha35VyQ'}}
|
||||
profile = result_data['profile']
|
||||
userid = profile['verifiedEmail']
|
||||
username = profile['preferredUsername']
|
||||
profile['emails'] = [{ 'value': userid, 'primary': True }]
|
||||
account = {'domain': domain,
|
||||
'userid': userid,
|
||||
'username': username }
|
||||
profile['accounts'] = [account]
|
||||
return result_data
|
|
@ -0,0 +1,38 @@
|
|||
"""OpenID Extensions
|
||||
|
||||
Additional OpenID extensions for OAuth and UIRequest extensions.
|
||||
|
||||
original code from velruse
|
||||
"""
|
||||
from openid import extension
|
||||
|
||||
class UIRequest(extension.Extension):
|
||||
"""OpenID UI extension"""
|
||||
ns_uri = 'http://specs.openid.net/extensions/ui/1.0'
|
||||
ns_alias = 'ui'
|
||||
|
||||
def __init__(self, mode=None, icon=False):
|
||||
super(UIRequest, self).__init__()
|
||||
self._args = {}
|
||||
if mode:
|
||||
self._args['mode'] = mode
|
||||
if icon:
|
||||
self._args['icon'] = str(icon).lower()
|
||||
|
||||
def getExtensionArgs(self):
|
||||
return self._args
|
||||
|
||||
|
||||
class OAuthRequest(extension.Extension):
|
||||
"""OAuth extension"""
|
||||
ns_uri = 'http://specs.openid.net/extensions/oauth/1.0'
|
||||
ns_alias = 'oauth'
|
||||
|
||||
def __init__(self, consumer, scope=None):
|
||||
super(OAuthRequest, self).__init__()
|
||||
self._args = {'consumer': consumer}
|
||||
if scope:
|
||||
self._args['scope'] = scope
|
||||
|
||||
def getExtensionArgs(self):
|
||||
return self._args
|
|
@ -0,0 +1,319 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from openid.consumer import consumer
|
||||
from openid.extensions import ax, sreg, pape
|
||||
from openid.store import memstore, filestore, sqlstore
|
||||
|
||||
from pylons import config, request, response, session, tmpl_context as c, url
|
||||
from pylons.controllers.util import abort, redirect
|
||||
|
||||
from linkdrop.lib.oauth.base import get_oauth_config
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
__all__ = ['OpenIDResponder']
|
||||
|
||||
|
||||
# Setup our attribute objects that we'll be requesting
|
||||
ax_attributes = dict(
|
||||
nickname = 'http://axschema.org/namePerson/friendly',
|
||||
email = 'http://axschema.org/contact/email',
|
||||
full_name = 'http://axschema.org/namePerson',
|
||||
birthday = 'http://axschema.org/birthDate',
|
||||
gender = 'http://axschema.org/person/gender',
|
||||
postal_code = 'http://axschema.org/contact/postalCode/home',
|
||||
country = 'http://axschema.org/contact/country/home',
|
||||
timezone = 'http://axschema.org/pref/timezone',
|
||||
language = 'http://axschema.org/pref/language',
|
||||
name_prefix = 'http://axschema.org/namePerson/prefix',
|
||||
first_name = 'http://axschema.org/namePerson/first',
|
||||
last_name = 'http://axschema.org/namePerson/last',
|
||||
middle_name = 'http://axschema.org/namePerson/middle',
|
||||
name_suffix = 'http://axschema.org/namePerson/suffix',
|
||||
web = 'http://axschema.org/contact/web/default',
|
||||
)
|
||||
|
||||
#Change names later to make things a little bit clearer
|
||||
alternate_ax_attributes = dict(
|
||||
nickname = 'http://schema.openid.net/namePerson/friendly',
|
||||
email = 'http://schema.openid.net/contact/email',
|
||||
full_name = 'http://schema.openid.net/namePerson',
|
||||
birthday = 'http://schema.openid.net/birthDate',
|
||||
gender = 'http://schema.openid.net/person/gender',
|
||||
postal_code = 'http://schema.openid.net/contact/postalCode/home',
|
||||
country = 'http://schema.openid.net/contact/country/home',
|
||||
timezone = 'http://schema.openid.net/pref/timezone',
|
||||
language = 'http://schema.openid.net/pref/language',
|
||||
name_prefix = 'http://schema.openid.net/namePerson/prefix',
|
||||
first_name = 'http://schema.openid.net/namePerson/first',
|
||||
last_name = 'http://schema.openid.net/namePerson/last',
|
||||
middle_name = 'http://schema.openid.net/namePerson/middle',
|
||||
name_suffix = 'http://schema.openid.net/namePerson/suffix',
|
||||
web = 'http://schema.openid.net/contact/web/default',
|
||||
)
|
||||
|
||||
# Translation dict for AX attrib names to sreg equiv
|
||||
trans_dict = dict(
|
||||
full_name = 'fullname',
|
||||
birthday = 'dob',
|
||||
postal_code = 'postcode',
|
||||
)
|
||||
|
||||
attributes = ax_attributes
|
||||
|
||||
|
||||
class AttribAccess(object):
|
||||
"""Uniform attribute accessor for Simple Reg and Attribute Exchange values"""
|
||||
def __init__(self, sreg_resp, ax_resp):
|
||||
self.sreg_resp = sreg_resp or {}
|
||||
self.ax_resp = ax_resp or ax.AXKeyValueMessage()
|
||||
|
||||
def get(self, key, ax_only=False):
|
||||
"""Get a value from either Simple Reg or AX"""
|
||||
# First attempt to fetch it from AX
|
||||
v = self.ax_resp.getSingle(attributes[key])
|
||||
if v:
|
||||
return v
|
||||
if ax_only:
|
||||
return None
|
||||
|
||||
# Translate the key if needed
|
||||
if key in trans_dict:
|
||||
key = trans_dict[key]
|
||||
|
||||
# Don't attempt to fetch keys that aren't valid sreg fields
|
||||
if key not in sreg.data_fields:
|
||||
return None
|
||||
|
||||
return self.sreg_resp.get(key)
|
||||
|
||||
|
||||
def extract_openid_data(identifier, sreg_resp, ax_resp):
|
||||
"""Extract the OpenID Data from Simple Reg and AX data
|
||||
|
||||
This normalizes the data to the appropriate format.
|
||||
|
||||
"""
|
||||
attribs = AttribAccess(sreg_resp, ax_resp)
|
||||
|
||||
ud = {'identifier': identifier}
|
||||
if 'google.com' in identifier:
|
||||
ud['providerName'] = 'Google'
|
||||
elif 'yahoo.com' in identifier:
|
||||
ud['providerName'] = 'Yahoo'
|
||||
else:
|
||||
ud['providerName'] = 'OpenID'
|
||||
|
||||
# Sort out the display name and preferred username
|
||||
if ud['providerName'] == 'Google':
|
||||
# Extract the first bit as the username since Google doesn't return
|
||||
# any usable nickname info
|
||||
email = attribs.get('email')
|
||||
if email:
|
||||
ud['preferredUsername'] = re.match('(^.*?)@', email).groups()[0]
|
||||
else:
|
||||
ud['preferredUsername'] = attribs.get('nickname')
|
||||
|
||||
# We trust that Google and Yahoo both verify their email addresses
|
||||
if ud['providerName'] in ['Google', 'Yahoo']:
|
||||
ud['verifiedEmail'] = attribs.get('email', ax_only=True)
|
||||
else:
|
||||
ud['emails'] = [attribs.get('email')]
|
||||
|
||||
# Parse through the name parts, assign the properly if present
|
||||
name = {}
|
||||
name_keys = ['name_prefix', 'first_name', 'middle_name', 'last_name', 'name_suffix']
|
||||
pcard_map = {'first_name': 'givenName', 'middle_name': 'middleName', 'last_name': 'familyName',
|
||||
'name_prefix': 'honorificPrefix', 'name_suffix': 'honorificSuffix'}
|
||||
full_name_vals = []
|
||||
for part in name_keys:
|
||||
val = attribs.get(part)
|
||||
if val:
|
||||
full_name_vals.append(val)
|
||||
name[pcard_map[part]] = val
|
||||
full_name = ' '.join(full_name_vals).strip()
|
||||
if not full_name:
|
||||
full_name = attribs.get('full_name')
|
||||
|
||||
name['formatted'] = full_name
|
||||
ud['name'] = name
|
||||
|
||||
ud['displayName'] = full_name or ud.get('preferredUsername')
|
||||
|
||||
urls = attribs.get('web')
|
||||
if urls:
|
||||
ud['urls'] = [urls]
|
||||
|
||||
for k in ['gender', 'birthday']:
|
||||
ud[k] = attribs.get(k)
|
||||
|
||||
# Now strip out empty values
|
||||
for k, v in ud.items():
|
||||
if not v or (isinstance(v, list) and not v[0]):
|
||||
del ud[k]
|
||||
|
||||
return ud
|
||||
|
||||
|
||||
class OpenIDResponder():
|
||||
"""OpenID Consumer for handling OpenID authentication
|
||||
"""
|
||||
|
||||
def __init__(self, provider):
|
||||
self.log_debug = logging.DEBUG >= log.getEffectiveLevel()
|
||||
|
||||
self.config = get_oauth_config(provider)
|
||||
|
||||
self.endpoint_regex = self.config.get('endpoint_regex')
|
||||
|
||||
# application config items, dont use self.config
|
||||
store = config.get('openid_store', 'mem')
|
||||
if store==u"file":
|
||||
store_file_path = config.get('openid_store_path', None)
|
||||
self.openid_store = filestore.FileOpenIDStore(store_file_path)
|
||||
elif store==u"mem":
|
||||
self.openid_store = memstore.MemoryStore()
|
||||
elif store==u"sql":
|
||||
# TODO: This does not work as we need a connection, not a string
|
||||
self.openid_store = sqlstore.SQLStore(sql_connstring, sql_associations_table, sql_connstring)
|
||||
|
||||
|
||||
def _lookup_identifier(self, identifier):
|
||||
"""Extension point for inherited classes that want to change or set
|
||||
a default identifier"""
|
||||
return identifier
|
||||
|
||||
def _update_authrequest(self, authrequest):
|
||||
"""Update the authrequest with the default extensions and attributes
|
||||
we ask for
|
||||
|
||||
This method doesn't need to return anything, since the extensions
|
||||
should be added to the authrequest object itself.
|
||||
|
||||
"""
|
||||
# Add on the Attribute Exchange for those that support that
|
||||
ax_request = ax.FetchRequest()
|
||||
for attrib in attributes.values():
|
||||
ax_request.add(ax.AttrInfo(attrib))
|
||||
authrequest.addExtension(ax_request)
|
||||
|
||||
# Form the Simple Reg request
|
||||
sreg_request = sreg.SRegRequest(
|
||||
optional=['nickname', 'email', 'fullname', 'dob', 'gender', 'postcode',
|
||||
'country', 'language', 'timezone'],
|
||||
)
|
||||
authrequest.addExtension(sreg_request)
|
||||
return None
|
||||
|
||||
def _get_access_token(self, request_token):
|
||||
"""Called to exchange a request token for the access token
|
||||
|
||||
This method doesn't by default return anything, other OpenID+Oauth
|
||||
consumers should override it to do the appropriate lookup for the
|
||||
access token, and return the access token.
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
def request_access(self):
|
||||
log_debug = self.log_debug
|
||||
if log_debug:
|
||||
log.debug('Handling OpenID login')
|
||||
|
||||
# Load default parameters that all Auth Responders take
|
||||
session['end_point_success'] = request.POST['end_point_success']
|
||||
session['end_point_auth_failure'] = request.POST['end_point_auth_failure']
|
||||
openid_url = request.POST.get('openid_identifier')
|
||||
|
||||
# Let inherited consumers alter the openid identifier if desired
|
||||
openid_url = self._lookup_identifier(openid_url)
|
||||
|
||||
if not openid_url or (self.endpoint_regex and not re.match(self.endpoint_regex, end_point)):
|
||||
return redirect(session['end_point_auth_failure'])
|
||||
|
||||
openid_session = {}
|
||||
oidconsumer = consumer.Consumer(openid_session, self.openid_store)
|
||||
|
||||
try:
|
||||
authrequest = oidconsumer.begin(openid_url)
|
||||
except consumer.DiscoveryFailure:
|
||||
return redirect(session['end_point_auth_failure'])
|
||||
|
||||
if authrequest is None:
|
||||
return redirect(session['end_point_auth_failure'])
|
||||
|
||||
# Update the authrequest
|
||||
self._update_authrequest(authrequest)
|
||||
|
||||
return_to = url(controller='account', action="verify",
|
||||
qualified=True)
|
||||
|
||||
# Ensure our session is saved for the id to persist
|
||||
session['openid_session'] = openid_session
|
||||
session.save()
|
||||
|
||||
redirect_url = authrequest.redirectURL(realm=request.application_url,
|
||||
return_to=return_to,
|
||||
immediate=False)
|
||||
return redirect(redirect_url)
|
||||
|
||||
# OpenID 2.0 lets Providers request POST instead of redirect, this
|
||||
# checks for such a request.
|
||||
if authrequest.shouldSendRedirect():
|
||||
redirect_url = authrequest.redirectURL(realm=request.application_url,
|
||||
return_to=return_to,
|
||||
immediate=False)
|
||||
return redirect(redirect_url)
|
||||
else:
|
||||
# XXX this will likely fail for now
|
||||
html = authrequest.htmlMarkup(realm=request.application_url, return_to=return_to,
|
||||
immediate=False)
|
||||
return response(body=html)
|
||||
|
||||
def verify(self):
|
||||
"""Handle incoming redirect from OpenID Provider"""
|
||||
log_debug = self.log_debug
|
||||
if log_debug:
|
||||
log.debug('Handling processing of response from server')
|
||||
|
||||
openid_session = session['openid_session']
|
||||
if not openid_session:
|
||||
raise Exception("openid session missing")
|
||||
|
||||
# Setup the consumer and parse the information coming back
|
||||
oidconsumer = consumer.Consumer(openid_session, self.openid_store)
|
||||
return_to = url(controller='account', action="verify",
|
||||
qualified=True)
|
||||
|
||||
info = oidconsumer.complete(request.params, return_to)
|
||||
|
||||
if info.status == consumer.FAILURE:
|
||||
raise Exception("consumer failure")
|
||||
elif info.status == consumer.CANCEL:
|
||||
raise Exception("consumer canceled")
|
||||
elif info.status == consumer.SUCCESS:
|
||||
openid_identity = info.identity_url
|
||||
if info.endpoint.canonicalID:
|
||||
# If it's an i-name, use the canonicalID as its secure even if
|
||||
# the old one is compromised
|
||||
openid_identity = info.endpoint.canonicalID
|
||||
|
||||
user_data = extract_openid_data(identifier=openid_identity,
|
||||
sreg_resp=sreg.SRegResponse.fromSuccessResponse(info),
|
||||
ax_resp=ax.FetchResponse.fromSuccessResponse(info))
|
||||
result_data = {'profile': user_data}
|
||||
# Did we get any OAuth info?
|
||||
access_token = None
|
||||
oauth = info.extensionResponse('http://specs.openid.net/extensions/oauth/1.0', False)
|
||||
if oauth and 'request_token' in oauth:
|
||||
access_token = self._get_access_token(oauth['request_token'])
|
||||
if access_token:
|
||||
result_data['oauth_token'] = access_token
|
||||
|
||||
return self._get_credentials(result_data)
|
||||
else:
|
||||
raise Exception("unknown openid failure")
|
||||
|
||||
def _get_credentials(self, access_token):
|
||||
return access_token
|
|
@ -21,7 +21,7 @@ class responder(OAuth1):
|
|||
self.access_token_url = 'https://twitter.com/oauth/access_token'
|
||||
self.authorization_url = 'https://twitter.com/oauth/authenticate'
|
||||
|
||||
def get_credentials(self, access_token):
|
||||
def _get_credentials(self, access_token):
|
||||
# XXX should call twitter.api.VerifyCredentials to get the user object
|
||||
# Setup the normalized poco contact object
|
||||
username = access_token['screen_name']
|
||||
|
|
|
@ -156,6 +156,31 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Google section -->
|
||||
<div class="section google hidden">
|
||||
<form id="oauthForm" action="/api/account/authorize" method="POST">
|
||||
<div class="row">
|
||||
<div class="c1 googleHeader">
|
||||
<strong>step 4:</strong> Add Google account *
|
||||
</div>
|
||||
<div class="c1">
|
||||
<input type="hidden" name="domain" value="google.com">
|
||||
<input type="hidden" name="scope" value="http://www.google.com/m8/feeds/">
|
||||
<input type="hidden" name="end_point_success" value="/scratch/oauth/#oauth_success_google_com">
|
||||
<input type="hidden" name="end_point_auth_failure" value="/scratch/oauth/#oauth_failure_google_com">
|
||||
<div class="googleActions">
|
||||
<a class="skip" href="#done">skip this step</a>
|
||||
<button>submit</button>
|
||||
</div>
|
||||
<div class="finePrint grey">
|
||||
*You may delete your account at any time
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- done section -->
|
||||
<div class="section done hidden">
|
||||
|
|
Загрузка…
Ссылка в новой задаче