Prepare Ipsilon patch for taiga POST
This commit is contained in:
parent
a9bffaf2e3
commit
56d363bbd5
2 changed files with 275 additions and 0 deletions
270
roles/ipsilon/files/openid_auth.py
Normal file
270
roles/ipsilon/files/openid_auth.py
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
# Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
|
||||||
|
|
||||||
|
from ipsilon.providers.common import ProviderPageBase
|
||||||
|
from ipsilon.providers.common import AuthenticationError, InvalidRequest
|
||||||
|
from ipsilon.providers.openid.meta import XRDSHandler, UserXRDSHandler
|
||||||
|
from ipsilon.providers.openid.meta import IDHandler
|
||||||
|
from ipsilon.util.policy import Policy
|
||||||
|
from ipsilon.util.trans import Transaction
|
||||||
|
from ipsilon.util.user import UserSession
|
||||||
|
|
||||||
|
from openid.server.server import ProtocolError, EncodingError
|
||||||
|
|
||||||
|
import cherrypy
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticateRequest(ProviderPageBase):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(AuthenticateRequest, self).__init__(*args, **kwargs)
|
||||||
|
self.stage = 'init'
|
||||||
|
self.trans = None
|
||||||
|
|
||||||
|
def _preop(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
# generate a new id or get current one
|
||||||
|
self.trans = Transaction('openid', **kwargs)
|
||||||
|
if self.trans.cookie.value != self.trans.provider:
|
||||||
|
self.debug('Invalid transaction, %s != %s' % (
|
||||||
|
self.trans.cookie.value, self.trans.provider))
|
||||||
|
except Exception, e: # pylint: disable=broad-except
|
||||||
|
raise cherrypy.HTTPRedirect('https://id.stg.fedoraproject.org/')
|
||||||
|
self.debug('Transaction initialization failed: %s' % repr(e))
|
||||||
|
raise cherrypy.HTTPError(400, 'Invalid transaction id')
|
||||||
|
|
||||||
|
def pre_GET(self, *args, **kwargs):
|
||||||
|
self._preop(*args, **kwargs)
|
||||||
|
|
||||||
|
def pre_POST(self, *args, **kwargs):
|
||||||
|
self._preop(*args, **kwargs)
|
||||||
|
|
||||||
|
def _get_form(self, *args):
|
||||||
|
form = None
|
||||||
|
if args is not None:
|
||||||
|
first = args[0] if len(args) > 0 else None
|
||||||
|
second = first[0] if len(first) > 0 else None
|
||||||
|
if isinstance(second, dict):
|
||||||
|
form = second.get('form', None)
|
||||||
|
return form
|
||||||
|
|
||||||
|
def auth(self, *args, **kwargs):
|
||||||
|
request = None
|
||||||
|
form = self._get_form(args)
|
||||||
|
try:
|
||||||
|
request = self._parse_request(**kwargs)
|
||||||
|
return self._openid_checks(request, form, **kwargs)
|
||||||
|
except InvalidRequest, e:
|
||||||
|
raise cherrypy.HTTPError(e.code, e.msg)
|
||||||
|
except AuthenticationError, e:
|
||||||
|
if request is None:
|
||||||
|
raise cherrypy.HTTPError(e.code, e.msg)
|
||||||
|
return self._respond(request.answer(False))
|
||||||
|
|
||||||
|
# get attributes, and apply policy mapping and filtering
|
||||||
|
def _source_attributes(self, session):
|
||||||
|
policy = Policy(self.cfg.default_attribute_mapping,
|
||||||
|
self.cfg.default_allowed_attributes)
|
||||||
|
userattrs = session.get_user_attrs()
|
||||||
|
mappedattrs, _ = policy.map_attributes(userattrs)
|
||||||
|
attributes = policy.filter_attributes(mappedattrs)
|
||||||
|
self.debug('Filterd attributes: %s' % repr(attributes))
|
||||||
|
return attributes
|
||||||
|
|
||||||
|
def _parse_request(self, **kwargs):
|
||||||
|
request = None
|
||||||
|
try:
|
||||||
|
request = self.cfg.server.decodeRequest(kwargs)
|
||||||
|
except ProtocolError, openid_error:
|
||||||
|
self.debug('ProtocolError: %s' % openid_error)
|
||||||
|
raise InvalidRequest('Invalid OpenID request', 400)
|
||||||
|
|
||||||
|
if request is None:
|
||||||
|
self.debug('No request')
|
||||||
|
raise cherrypy.HTTPRedirect(self.basepath)
|
||||||
|
|
||||||
|
return request
|
||||||
|
|
||||||
|
def _openid_checks(self, request, form, **kwargs):
|
||||||
|
us = UserSession()
|
||||||
|
user = us.get_user()
|
||||||
|
immediate = False
|
||||||
|
|
||||||
|
self.debug('Mode: %s Stage: %s User: %s' % (
|
||||||
|
kwargs['openid.mode'], self.stage, user.name))
|
||||||
|
if kwargs.get('openid.mode', None) == 'checkid_setup':
|
||||||
|
if user.is_anonymous:
|
||||||
|
if self.stage == 'init':
|
||||||
|
returl = '%s/openid/Continue?%s' % (
|
||||||
|
self.basepath, self.trans.get_GET_arg())
|
||||||
|
data = {'openid_stage': 'auth',
|
||||||
|
'openid_request': json.dumps(kwargs),
|
||||||
|
'login_return': returl,
|
||||||
|
'login_target': request.trust_root}
|
||||||
|
self.trans.store(data)
|
||||||
|
redirect = '%s/login?%s' % (self.basepath,
|
||||||
|
self.trans.get_GET_arg())
|
||||||
|
self.debug('Redirecting: %s' % redirect)
|
||||||
|
raise cherrypy.HTTPRedirect(redirect)
|
||||||
|
else:
|
||||||
|
raise AuthenticationError("unknown user", 401)
|
||||||
|
|
||||||
|
elif kwargs.get('openid.mode', None) == 'checkid_immediate':
|
||||||
|
# This is immediate, so we need to assert or fail
|
||||||
|
if user.is_anonymous:
|
||||||
|
return self._respond(request.answer(False))
|
||||||
|
|
||||||
|
immediate = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
return self._respond(self.cfg.server.handleRequest(request))
|
||||||
|
|
||||||
|
# check if this is discovery or needs identity matching checks
|
||||||
|
if not request.idSelect():
|
||||||
|
idurl = self.cfg.identity_url_template % {'username': user.name}
|
||||||
|
if request.identity != idurl:
|
||||||
|
raise AuthenticationError("User ID mismatch!", 401)
|
||||||
|
|
||||||
|
# check if the relying party is trusted
|
||||||
|
if request.trust_root in self.cfg.untrusted_roots:
|
||||||
|
raise AuthenticationError("Untrusted Relying party", 401)
|
||||||
|
|
||||||
|
# if the party is explicitly whitelisted just respond
|
||||||
|
if request.trust_root in self.cfg.trusted_roots:
|
||||||
|
return self._respond(self._response(request, us))
|
||||||
|
|
||||||
|
allowroot = 'allow-%s' % request.trust_root
|
||||||
|
|
||||||
|
try:
|
||||||
|
userdata = user.load_plugin_data(self.cfg.name)
|
||||||
|
expiry = int(userdata[allowroot])
|
||||||
|
except Exception, e: # pylint: disable=broad-except
|
||||||
|
self.debug(e)
|
||||||
|
expiry = 0
|
||||||
|
if expiry > int(time.time()):
|
||||||
|
self.debug("User has unexpired previous authorization")
|
||||||
|
return self._respond(self._response(request, us))
|
||||||
|
|
||||||
|
if immediate:
|
||||||
|
raise AuthenticationError("No consent for immediate", 401)
|
||||||
|
|
||||||
|
if self.stage == 'consent':
|
||||||
|
if form is None:
|
||||||
|
raise AuthenticationError("Unintelligible consent", 401)
|
||||||
|
allow = form.get('decided_allow', False)
|
||||||
|
if not allow:
|
||||||
|
raise AuthenticationError("User declined", 401)
|
||||||
|
try:
|
||||||
|
days = int(form.get('remember_for_days', '0'))
|
||||||
|
if days < 0 or days > 7:
|
||||||
|
raise
|
||||||
|
userdata = {allowroot: str(int(time.time()) + (days*86400))}
|
||||||
|
user.save_plugin_data(self.cfg.name, userdata)
|
||||||
|
except Exception, e: # pylint: disable=broad-except
|
||||||
|
self.debug(e)
|
||||||
|
days = 0
|
||||||
|
|
||||||
|
# all done we consent!
|
||||||
|
return self._respond(self._response(request, us))
|
||||||
|
|
||||||
|
else:
|
||||||
|
data = {'openid_stage': 'consent',
|
||||||
|
'openid_request': json.dumps(kwargs)}
|
||||||
|
self.trans.store(data)
|
||||||
|
|
||||||
|
# Add extension data to this dictionary
|
||||||
|
ad = {
|
||||||
|
"Trust Root": request.trust_root,
|
||||||
|
}
|
||||||
|
userattrs = self._source_attributes(us)
|
||||||
|
for n, e in self.cfg.extensions.available().items():
|
||||||
|
data = e.get_display_data(request, userattrs)
|
||||||
|
self.debug('%s returned %s' % (n, repr(data)))
|
||||||
|
for key, value in data.items():
|
||||||
|
ad[self.cfg.mapping.display_name(key)] = value
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"title": 'Consent',
|
||||||
|
"action": '%s/openid/Consent' % (self.basepath),
|
||||||
|
"trustroot": request.trust_root,
|
||||||
|
"username": user.name,
|
||||||
|
"authz_details": ad,
|
||||||
|
}
|
||||||
|
context.update(dict((self.trans.get_POST_tuple(),)))
|
||||||
|
return self._template('openid/consent_form.html', **context)
|
||||||
|
|
||||||
|
def _response(self, request, session):
|
||||||
|
user = session.get_user()
|
||||||
|
identity_url = self.cfg.identity_url_template % {'username': user.name}
|
||||||
|
response = request.answer(
|
||||||
|
True,
|
||||||
|
identity=identity_url,
|
||||||
|
claimed_id=identity_url
|
||||||
|
)
|
||||||
|
userattrs = self._source_attributes(session)
|
||||||
|
for _, e in self.cfg.extensions.available().items():
|
||||||
|
resp = e.get_response(request, userattrs)
|
||||||
|
if resp is not None:
|
||||||
|
response.addExtension(resp)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _respond(self, response):
|
||||||
|
try:
|
||||||
|
self.debug('Response: %s' % response)
|
||||||
|
webresponse = self.cfg.server.encodeResponse(response)
|
||||||
|
cherrypy.response.headers.update(webresponse.headers)
|
||||||
|
cherrypy.response.status = webresponse.code
|
||||||
|
return webresponse.body
|
||||||
|
except EncodingError, encoding_error:
|
||||||
|
self.debug('Unable to respond because: %s' % encoding_error)
|
||||||
|
cherrypy.response.headers = {
|
||||||
|
'Content-Type': 'text/plain; charset=UTF-8'
|
||||||
|
}
|
||||||
|
cherrypy.response.status = 400
|
||||||
|
return encoding_error.response.encodeToKVForm()
|
||||||
|
|
||||||
|
|
||||||
|
class Continue(AuthenticateRequest):
|
||||||
|
|
||||||
|
def GET(self, *args, **kwargs):
|
||||||
|
transdata = self.trans.retrieve()
|
||||||
|
self.stage = transdata.get('openid_stage', None)
|
||||||
|
openid_request = transdata.get('openid_request', None)
|
||||||
|
if self.stage is None or openid_request is None:
|
||||||
|
raise AuthenticationError("unknown state", 400)
|
||||||
|
|
||||||
|
kwargs = json.loads(openid_request)
|
||||||
|
return self.auth(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Consent(AuthenticateRequest):
|
||||||
|
|
||||||
|
def POST(self, *args, **kwargs):
|
||||||
|
transdata = self.trans.retrieve()
|
||||||
|
self.stage = transdata.get('openid_stage', None)
|
||||||
|
openid_request = transdata.get('openid_request', None)
|
||||||
|
if self.stage is None or openid_request is None:
|
||||||
|
raise AuthenticationError("unknown state", 400)
|
||||||
|
|
||||||
|
args = ({'form': kwargs},)
|
||||||
|
kwargs = json.loads(openid_request)
|
||||||
|
return self.auth(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenID(AuthenticateRequest):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(OpenID, self).__init__(*args, **kwargs)
|
||||||
|
self.XRDS = XRDSHandler(*args, **kwargs)
|
||||||
|
self.yadis = UserXRDSHandler(*args, **kwargs)
|
||||||
|
self.id = IDHandler(*args, **kwargs)
|
||||||
|
self.Continue = Continue(*args, **kwargs)
|
||||||
|
self.Consent = Consent(*args, **kwargs)
|
||||||
|
self.trans = None
|
||||||
|
|
||||||
|
def GET(self, *args, **kwargs):
|
||||||
|
return self.auth(**kwargs)
|
||||||
|
|
||||||
|
def POST(self, *args, **kwargs):
|
||||||
|
return self.auth(**kwargs)
|
|
@ -24,6 +24,11 @@
|
||||||
dest=/usr/lib/python2.7/site-packages/ipsilon/providers/openid/extensions/api.py
|
dest=/usr/lib/python2.7/site-packages/ipsilon/providers/openid/extensions/api.py
|
||||||
owner=root group=root mode=0644
|
owner=root group=root mode=0644
|
||||||
|
|
||||||
|
- name: Apply hotfix for taiga to get POST results
|
||||||
|
copy: src=openid_auth.py
|
||||||
|
dest=/usr/lib/python2.7/site-packages/ipsilon/providers/openid/auth.py
|
||||||
|
owner=root group=root mode=0644
|
||||||
|
|
||||||
- name: copy ipsilon templates
|
- name: copy ipsilon templates
|
||||||
copy: src=templates/
|
copy: src=templates/
|
||||||
dest=/usr/share/ipsilon/templates-fedora
|
dest=/usr/share/ipsilon/templates-fedora
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue