Commit 475e2f70 authored by Christophe de Vienne's avatar Christophe de Vienne
Browse files

Add autologin capabilities.

Closes #3957660
parent b4722f569831
......@@ -47,6 +47,36 @@ class FacebookProvider(Provider):
'username': me.get('username')
}
def autologin_script(self, service):
return """
$.getScript('//connect.facebook.net/%(lang)s/all.js', function(){
FB.init({
appId: '%(app_id)s',
});
FB.getLoginStatus(function (response) {
if (response.status == 'connected') {
$.ajax('/externalauth-userinfos', {
data: {
__externalauthprovider: "facebook",
__externalauth_oauth2_token:
response.authResponse.accessToken,
autologin: true
},
dataType: 'json',
success: function (data) {
if (!data.anonymous) {
document.location.reload();
}
}
});
}
});
});
""" % {
'lang': 'en_UK',
'app_id': service.application_id
}
class ServiceAdapter(EntityAdapter):
__regid__ = 'externalauth.service'
......@@ -68,3 +98,6 @@ class ServiceAdapter(EntityAdapter):
access_token_url=provider.access_token_url,
base_url=provider.base_url,
)
def autologin_script(self):
return self.provider.autologin_script(self.entity)
add_attribute('ExternalIdentity', 'autologin')
sync_schema_props_perms('ExternalIdentity')
......@@ -22,7 +22,7 @@ from yams.buildobjs import (
String, EntityType, SubjectRelation, Datetime, Boolean
)
from cubicweb.schema import ERQLExpression
from cubicweb.schema import ERQLExpression, RRQLExpression
# TODO set the maxsize of the String attributes.
......@@ -87,7 +87,7 @@ class ExternalIdentity(EntityType):
__permissions__ = {
'read': ('managers', ERQLExpression('X identity_of U')),
'add': ('managers',),
'update': ('managers',),
'update': ('managers', ERQLExpression('X identity_of U')),
'delete': ('managers', ERQLExpression('X identity_of U'))
}
__unique_together__ = [('provider', 'uid')]
......@@ -116,6 +116,13 @@ class ExternalIdentity(EntityType):
'update': ('managers',),
}
)
autologin = Boolean(
required=True, default=False,
__permissions__={
'read': ('managers', 'users'),
'update': ('managers', ERQLExpression('X identity_of U')),
}
)
class OAuth2Session(EntityType):
......
......@@ -151,11 +151,14 @@ class OAuthTC(CubicWebTestTC):
self.vreg.unregister(FakeServiceAdapter)
super(OAuthTC, self).tearDown()
def login(self, oauthtoken=None):
def login(self, oauthtoken=None, autologin=None):
if oauthtoken:
return self.webapp.get(
url = (
'?__externalauthprovider=facebook'
'&__externalauth_oauth2_token=%s' % oauthtoken)
if autologin:
url += '&autologin=1'
return self.webapp.get(url)
response = self.webapp.get('?__externalauthprovider=facebook')
location = response.headers['location']
......@@ -323,3 +326,30 @@ class OAuthTC(CubicWebTestTC):
self.login(oauthtoken="SOMETOKEN")
self.assertEqual('zeuser', self.webapp.cookies['__data_session'][:6])
def test_persistent_login(self):
with self.admin_access.repo_cnx() as cnx:
user = cnx.create_entity(
'CWUser', login=u"zeuser", upassword='',
in_group=cnx.find('CWGroup', name='users').one())
cnx.create_entity(
'ExternalIdentity',
identity_of=user, provider=cnx.find(
'ExternalAuthProvider', spid='facebook').one(),
uid=u'1111')
rset = cnx.execute('Any X WHERE X login "zeuser"')
self.assertEqual(1, rset.rowcount)
cnx.commit()
self.login()
self.assertEqual('zeuser', self.webapp.cookies['__data_session'][:6])
self.webapp.cookiejar.clear()
self.login(oauthtoken="SOMETOKEN", autologin=True)
self.assertEqual('zeuser', self.webapp.cookies['__data_session'][:6])
self.logout()
self.login(oauthtoken="SOMETOKEN2", autologin=True)
self.assertNotIn('__data_session', self.webapp.cookies)
......@@ -15,6 +15,7 @@
"""cubicweb-oauth views/forms/actions/components for web ui"""
import json
import re
import urllib
import urlparse
......@@ -28,7 +29,7 @@ from cubicweb.view import View
from cubicweb.web.controller import Controller
from cubicweb.web.views import authentication
from cubicweb.web.views import basecomponents, basetemplates
from cubicweb.web import Redirect, formfields, formwidgets
from cubicweb.web import Redirect, formfields, formwidgets, LogOut
from cubicweb.web import InvalidSession
......@@ -69,6 +70,20 @@ class ExternalAuthMixin(object):
))
class ExternalAuthUserInfos(Controller):
__regid__ = 'externalauth-userinfos'
def publish(self, rset=None):
if self._cw.session.anonymous_session:
ret = {'anonymous': True}
else:
ret = {
'anonymous': False,
'login': self._cw.user.login
}
return json.dumps(ret)
class ExternalAuthConfirm(Controller):
__regid__ = 'externalauth-confirm'
__select__ = match_form_params('code', '__externalauth_negociationid')
......@@ -163,7 +178,7 @@ class ExternalAuthRetrieverStart(
class ExternalAuthRetrieverMixin(ExternalAuthMixin):
def _authentication_information(
self, req, cnx, adapter, service, oauth2_session):
self, req, cnx, adapter, service, oauth2_session, autologin=False):
try:
infos = adapter.provider.retrieve_info(oauth2_session)
extid_rset = cnx.execute(
......@@ -174,6 +189,11 @@ class ExternalAuthRetrieverMixin(ExternalAuthMixin):
assert extid_rset.rowcount < 2
if extid_rset:
external_identity = extid_rset.get_entity(0, 0)
if autologin and not external_identity.autologin:
self.info(
"Autologin is not enabled for identity %s",
infos['uid'])
raise authentication.NoAuthInfo
else:
external_identity = cnx.create_entity(
'ExternalIdentity', provider=service.provider[0],
......@@ -196,15 +216,19 @@ class ExternalAuthRetrieverMixin(ExternalAuthMixin):
)
cnx.commit(free_cnxset=False)
except authentication.NoAuthInfo:
raise
except Exception, e:
self.exception("Cannot get the oauth2_session")
raise authentication.NoAuthInfo(str(e))
if external_identity.identity_of:
user = external_identity.identity_of[0]
self.debug('identified %s on %s as %s' % (
user.login, service.provider[0].name, infos['uid']))
login = user.login
external_identity.cw_set(autologin=True)
cnx.commit()
self.info('identified %s on %s as %s' % (
user.login, service.provider[0].name, infos['uid']))
else:
self.debug(
"Unknown identity %s on %s: will create a local account",
......@@ -235,7 +259,10 @@ class ExternalAuthRetrieverMixin(ExternalAuthMixin):
{'sieid': external_identity.eid,
'login': login}
)
external_identity.cw_set(autologin=True)
cnx.commit()
self.info('created user %s on %s as %s' % (
user.login, service.provider[0].name, infos['uid']))
except:
self.exception("Cannot create user %s" % login)
raise
......@@ -264,6 +291,7 @@ class ExternalAuthRetrieverDirect(
spid = req.form.get('__externalauthprovider')
oauth2_token = req.form.get('__externalauth_oauth2_token')
autologin = bool(req.form.get('autologin'))
vreg = self._cw # YES, see cw/web/views/authentication.py ~106
repo = vreg.config.repository(vreg)
......@@ -277,7 +305,8 @@ class ExternalAuthRetrieverDirect(
self.error("Cannot get a auth2session")
raise authentication.NoAuthInfo
return self._authentication_information(
req, cnx, adapter, service, oauth2_session)
req, cnx, adapter, service, oauth2_session,
autologin=autologin)
class ExternalAuthRetrieverFinish(
......@@ -385,7 +414,7 @@ class ExternalAuthLogForm(basetemplates.BaseLogForm):
onclick_args = ('externalauthlogbox', '__externalauthprovider')
class ExternalAuthLoginLink(basecomponents.HeaderComponent):
class ExternalAuthLoginLink(ExternalAuthMixin, basecomponents.HeaderComponent):
__regid__ = 'externalauthlink'
__select__ = (
basecomponents.HeaderComponent.__select__ and
......@@ -396,6 +425,15 @@ class ExternalAuthLoginLink(basecomponents.HeaderComponent):
context = _('header-right')
onclick = "javascript: cw.htmlhelpers.popupLoginBox('%s', '%s');"
def autologin_script(self):
with self._cw.cnx._repo.internal_cnx() as cnx:
for spid in ('facebook',):
self._cw.html_headers.add_post_inline_script(
self.get_service(self._cw, cnx, spid).cw_adapt_to(
'externalauth.service'
).autologin_script()
)
def render(self, w):
text = self._cw._('external auth login')
w(u"""<a href="#" onclick="%s">%s</a>""" % (
......@@ -406,6 +444,7 @@ class ExternalAuthLoginLink(basecomponents.HeaderComponent):
'externalauthlogform', rset=self.cw_rset,
id='externalauthlogbox', w=w
)
self.autologin_script()
class ExternalAuthLogFormView(View):
......@@ -426,3 +465,17 @@ Select the provider you want to use to authenticate.
form = self._cw.vreg['forms'].select('externalauthlogform', self._cw)
form.render(w=w)
w(u'</div>')
# XXX Hijack the logout method.
# We should have a hook for pre/post-logout actions
def CookieSessionHandler_logout(self, req, goto_url):
if req.user.reverse_identity_of:
req.user.reverse_identity_of[0].cw_set(autologin=False)
req.cnx.commit()
self.session_manager.close_session(req.session)
req.remove_cookie(self.session_cookie(req))
raise LogOut(url=goto_url)
from cubicweb.web.application import CookieSessionHandler
CookieSessionHandler.logout = CookieSessionHandler_logout
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment