-
-
Save mcdonc/15096411bf36a71718f7 to your computer and use it in GitHub Desktop.
from wsgiref.simple_server import make_server | |
from zope.interface import ( | |
Interface, | |
alsoProvides, | |
) | |
from pyramid.config import Configurator | |
from pyramid.httpexceptions import HTTPFound | |
from pyramid.response import Response | |
from pyramid.security import Allow, remember, forget | |
from pyramid.authorization import ACLAuthorizationPolicy | |
from pyramid.authentication import AuthTktAuthenticationPolicy | |
from pyramid.view import ( | |
view_config, | |
forbidden_view_config, | |
) | |
class ICase(Interface): pass | |
class IFooCase(ICase): pass | |
class IBarCase(ICase): pass | |
cases = { | |
'1':{'name':'Fred', 'type':'foo'}, | |
'2':{'name':'Jane', 'type':'bar'}, | |
} | |
type_to_interface = { | |
'foo':IFooCase, | |
'bar':IBarCase, | |
} | |
users = { | |
'allcases':{ | |
'name':'All Cases', | |
'groups':('g:foocase_viewer', 'g:barcase_viewer'), | |
}, | |
'foocases':{ | |
'name':'Foo Cases', | |
'groups':('g:foocase_viewer', ), | |
} | |
} | |
def groupfinder(userid, request): | |
user = users.get(userid) | |
if user is not None: | |
return user['groups'] | |
class CaseContext(object): | |
def __init__(self, request): | |
case_id = request.matchdict['id'] | |
self.case = cases[case_id] | |
self.case_type = self.case['type'] | |
iface = type_to_interface[self.case_type] | |
alsoProvides(self, iface) # either IFooCase or IBarCase | |
def __acl__(self): | |
return [ | |
(Allow, 'g:%scase_viewer' % self.case_type, 'view') | |
] | |
@view_config( | |
route_name='root', | |
) | |
def root(request): | |
body = ''' | |
<html> | |
<head> | |
</head> | |
<body> | |
<p> Logged in as: %s </p> | |
<p> Intent: | |
<ul> | |
<li>A user who is not logged in will be shown the forbidden page for both /cases/1 and /cases/2 </li> | |
<li>Someone logged in as 'foocases' will be be shown the show_foo_case view when they visit /cases/1 (a Foo case), but will be shown the forbidden_case view when they visit /cases/2 (a Bar case) </li> | |
<li>Someone logged in as 'allcases' will be be shown the show_foo_case view when they visit /cases/1 (a Foo case), they will be shown the show_bar_case view when they visit the /cases/2 (a Bar case) </li> | |
</ul> | |
</p> | |
<ul> | |
<li> <a href="/login?userid=foocases">Log In As foocases</a> </li> | |
<li> <a href="/login?userid=allcases">Log In As allcases</a> </li> | |
<li> <a href="/logout">Log Out</a> </li> | |
</ul> | |
<ul> | |
<li> <a href="/cases/1">/cases/1 (a Foo case)</a> </li> | |
<li> <a href="/cases/2">/cases/2 (a Bar case)</a> </li> | |
</ul> | |
</body> | |
</html>''' % request.authenticated_userid | |
return Response( | |
content_type='text/html', | |
body = body, | |
) | |
@view_config( | |
route_name='show_case', | |
context=IFooCase, | |
permission='view', | |
renderer='string' | |
) | |
def show_foo_case(context, request): | |
return 'show_foo_case: This foo case is named %(name)s' % context.case | |
@view_config( | |
route_name='show_case', | |
context=IBarCase, | |
permission='view', | |
renderer='string' | |
) | |
def show_bar_case(context, request): | |
return 'show_bar_case: This bar case is named %(name)s' % context.case | |
@forbidden_view_config( | |
renderer='string', | |
route_name='show_case', | |
) | |
def forbidden_case(request): | |
# cant use ``context=`` in the view configuration here, as the context | |
# will be an HTTPForbidden exception object when we get here. However, | |
# request.context will be the original CaseContext instance, so we | |
# can perform some sort of dispatch on that as necessary. | |
return ('forbidden_case: You are forbidden to see the case of ' | |
'type %(type)s named %(name)s' % request.context.case) | |
@view_config( | |
route_name='login', | |
) | |
def login_view(request): | |
userid = request.GET.get('userid') | |
user = users.get(userid) | |
if user is not None: | |
headers = remember(request, userid) | |
return HTTPFound( | |
location=request.route_url('root'), | |
headers=headers | |
) | |
return HTTPFound(location=request.route_url('login')) | |
@view_config( | |
route_name='logout', | |
) | |
def logout_view(request): | |
headers = forget(request) | |
return HTTPFound(location=request.route_url('root'), headers=headers) | |
if __name__ == '__main__': | |
authz_policy = ACLAuthorizationPolicy() | |
authn_policy = AuthTktAuthenticationPolicy( | |
'sosecret', | |
callback=groupfinder | |
) | |
config = Configurator( | |
authentication_policy=authn_policy, | |
authorization_policy=authz_policy, | |
) | |
config.add_route('root', '/') | |
config.add_route('login', '/login') | |
config.add_route('logout', '/logout') | |
config.add_route('show_case', '/cases/{id}', factory=CaseContext) | |
config.scan() | |
app = config.make_wsgi_app() | |
server = make_server('0.0.0.0', 8080, app) | |
server.serve_forever() |
contains a pyramid app that will listen on 8080 if you run it with any python interpreter that has pyramid installed
connect on http://localhost:8080/
The forbidden view configured in the above app only applies to forbidden errors generated when the route_name is "show_case", the other generic one you already have will fire when that is not true.
Ugh, so yeah if a non signed in user goes back to a one of those pages they would see the you should buy page... and not get redirected back to the login page with the correct target_url set
Current forbidden_view
@view_config(context=HTTPForbidden,
permission=NO_PERMISSION_REQUIRED,
xhr=False)
def forbidden_view(request):
if Authenticated not in effective_principals(request) or not request.user:
request.session['target_url'] = request.url
return HTTPFound(request.route_url('login'))
if 'group:confirmed' not in effective_principals(request):
with transaction.manager:
send_confirmation_email(request=request, user=request.user,
login_attempt=True)
error_msg = '<strong>Unable to Sign in</strong>: Must confirm email address \
and accept Terms of Use to log in.<br />Your confirmation email has \
been re-sent.'
request.session.flash(error_msg, 'error', allow_duplicate=False)
request.response.headerlist.extend(
forget(request),
)
return HTTPFound(request.route_url('login'))
if 'group:confirmed' in effective_principals(request):
# Attempt to access page available only to logged out users.
return HTTPFound(request.route_url('landing'))
Isn't our permissions case actually pretty specific here? Like @k9 pointed out, it's more like we're manifesting different contexts based on the user's package, and then rendering a different template based on that context. Which feels kind of like...
@view_config(
route_name='show_case',
context=IPermittedCase,
permission='view',
renderer='fullcase.mako'
)
@view_config(
route_name='show_case',
context=IBannedCase,
permission='view',
renderer='upsellcase.mako'
)
def show_case(context, request):
return {} # Maybe including some specific info depending on context?
How would a generic forbidden
no_login
at all interact with the specificforbidden_view_config
?