- update wiki authorization step and its src files
- trim index
| | |
| | | ==================== |
| | | |
| | | :app:`Pyramid` provides facilities for :term:`authentication` and |
| | | ::term:`authorization`. We'll make use of both features to provide security |
| | | :to our application. Our application currently allows anyone with access to |
| | | :the server to view, edit, and add pages to our wiki. We'll change that to |
| | | :allow only people who are members of a *group* named ``group:editors`` to add |
| | | :and edit wiki pages but we'll continue allowing anyone with access to the |
| | | :server to view pages. |
| | | :term:`authorization`. We'll make use of both features to provide security to |
| | | our application. Our application currently allows anyone with access to the |
| | | server to view, edit, and add pages to our wiki. We'll change that to allow |
| | | only people who are members of a *group* named ``group:editors`` to add and |
| | | edit wiki pages, but we'll continue allowing anyone with access to the server |
| | | to view pages. |
| | | |
| | | We will also add a login page and a logout link on all the pages. The login |
| | | page will be shown when a user is denied access to any of the views that |
| | |
| | | Add users and groups |
| | | ~~~~~~~~~~~~~~~~~~~~ |
| | | |
| | | Create a new ``tutorial/tutorial/security.py`` module with the |
| | | Create a new ``tutorial/security.py`` module with the |
| | | following content: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/security.py |
| | |
| | | Add an ACL |
| | | ~~~~~~~~~~ |
| | | |
| | | Open ``tutorial/tutorial/models.py`` and add the following import |
| | | Open ``tutorial/models.py`` and add the following import |
| | | statement at the head: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/models.py |
| | |
| | | Add authentication and authorization policies |
| | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| | | |
| | | Open ``tutorial/tutorial/__init__.py`` and add the highlighted import |
| | | Open ``tutorial/__init__.py`` and add the highlighted import |
| | | statements: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/__init__.py |
| | |
| | | |
| | | Add permission declarations |
| | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| | | Open ``tutorial/tutorial/views.py`` and add a ``permission='edit'`` parameter |
| | | Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter |
| | | to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/views.py |
| | |
| | | redirect back to the front page. |
| | | |
| | | Add the following import statements to the head of |
| | | ``tutorial/tutorial/views.py``: |
| | | ``tutorial/views.py``: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/views.py |
| | | :lines: 6-17 |
| | |
| | | Add the ``login.pt`` Template |
| | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| | | |
| | | Create ``tutorial/tutorial/templates/login.pt`` with the following content: |
| | | Create ``tutorial/templates/login.pt`` with the following content: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/templates/login.pt |
| | | :language: html |
| | |
| | | Return a ``logged_in`` flag to the renderer |
| | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| | | |
| | | Open ``tutorial/tutorial/views.py`` again. Add a ``logged_in`` parameter to |
| | | Open ``tutorial/views.py`` again. Add a ``logged_in`` parameter to |
| | | the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as |
| | | follows: |
| | | |
| | |
| | | Add a "Logout" link when logged in |
| | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| | | |
| | | Open ``tutorial/tutorial/templates/edit.pt`` and |
| | | ``tutorial/tutorial/templates/view.pt`` and add the following code as |
| | | Open ``tutorial/templates/edit.pt`` and |
| | | ``tutorial/templates/view.pt`` and add the following code as |
| | | indicated by the highlighted lines. |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/templates/edit.pt |
| | |
| | | Reviewing our changes |
| | | --------------------- |
| | | |
| | | Our ``tutorial/tutorial/__init__.py`` will look like this when we're done: |
| | | Our ``tutorial/__init__.py`` will look like this when we're done: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/__init__.py |
| | | :linenos: |
| | |
| | | |
| | | Only the highlighted lines need to be added or edited. |
| | | |
| | | Our ``tutorial/tutorial/models.py`` will look like this when we're done: |
| | | Our ``tutorial/models.py`` will look like this when we're done: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/models.py |
| | | :linenos: |
| | |
| | | |
| | | Only the highlighted lines need to be added or edited. |
| | | |
| | | Our ``tutorial/tutorial/views.py`` will look like this when we're done: |
| | | Our ``tutorial/views.py`` will look like this when we're done: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/views.py |
| | | :linenos: |
| | |
| | | |
| | | Only the highlighted lines need to be added or edited. |
| | | |
| | | Our ``tutorial/tutorial/templates/edit.pt`` template will look like this when |
| | | Our ``tutorial/templates/edit.pt`` template will look like this when |
| | | we're done: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/templates/edit.pt |
| | |
| | | |
| | | Only the highlighted lines need to be added or edited. |
| | | |
| | | Our ``tutorial/tutorial/templates/view.pt`` template will look like this when |
| | | Our ``tutorial/templates/view.pt`` template will look like this when |
| | | we're done: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/templates/view.pt |
| | |
| | | authorization |
| | | tests |
| | | distributing |
| | | |
| | |
| | | 0.0 |
| | | --- |
| | | |
| | | - Initial version |
| | | |
| | | - Initial version |
| | |
| | | ### |
| | | # app configuration |
| | | # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html |
| | | # http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html |
| | | ### |
| | | |
| | | [app:main] |
| | |
| | | |
| | | [server:main] |
| | | use = egg:waitress#main |
| | | host = 0.0.0.0 |
| | | host = 127.0.0.1 |
| | | port = 6543 |
| | | |
| | | ### |
| | | # logging configuration |
| | | # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html |
| | | # http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html |
| | | ### |
| | | |
| | | [loggers] |
| | |
| | | formatter = generic |
| | | |
| | | [formatter_generic] |
| | | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s |
| | | format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s |
| | |
| | | ### |
| | | # app configuration |
| | | # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html |
| | | # http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html |
| | | ### |
| | | |
| | | [app:main] |
| | |
| | | |
| | | ### |
| | | # logging configuration |
| | | # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html |
| | | # http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html |
| | | ### |
| | | |
| | | [loggers] |
| | |
| | | formatter = generic |
| | | |
| | | [formatter_generic] |
| | | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s |
| | | format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s |
| | |
| | | 'docutils', |
| | | ] |
| | | |
| | | testing_extras = [ |
| | | 'WebTest >= 1.3.1', # py3 compat |
| | | 'pytest', # includes virtualenv |
| | | 'pytest-cov', |
| | | ] |
| | | |
| | | setup(name='tutorial', |
| | | version='0.0', |
| | | description='tutorial', |
| | | long_description=README + '\n\n' + CHANGES, |
| | | classifiers=[ |
| | | "Programming Language :: Python", |
| | | "Framework :: Pyramid", |
| | | "Topic :: Internet :: WWW/HTTP", |
| | | "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", |
| | | ], |
| | | "Programming Language :: Python", |
| | | "Framework :: Pyramid", |
| | | "Topic :: Internet :: WWW/HTTP", |
| | | "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", |
| | | ], |
| | | author='', |
| | | author_email='', |
| | | url='', |
| | |
| | | packages=find_packages(), |
| | | include_package_data=True, |
| | | zip_safe=False, |
| | | extras_require={ |
| | | 'testing': testing_extras, |
| | | }, |
| | | install_requires=requires, |
| | | tests_require=requires, |
| | | test_suite="tutorial", |
| | | entry_points="""\ |
| | | [paste.app_factory] |
| | | main = tutorial:main |
| | |
| | | self.data = data |
| | | |
| | | def appmaker(zodb_root): |
| | | if not 'app_root' in zodb_root: |
| | | if 'app_root' not in zodb_root: |
| | | app_root = Wiki() |
| | | frontpage = Page('This is the front page') |
| | | app_root['FrontPage'] = frontpage |
| | |
| | | @import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} |
| | | @import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a,a{color:#f2b7bd;text-decoration:underline}.starter-template .links ul li a:hover,a:hover{color:#fff;text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} |
| | |
| | | <div class="col-md-10"> |
| | | <div class="content"> |
| | | <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1> |
| | | <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p> |
| | | <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="row"> |
| | | <div class="links"> |
| | | <ul> |
| | | <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li> |
| | | <li class="current-version">Generated by v1.7</li> |
| | | <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li> |
| | | <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> |
| | | <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> |
| | | <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> |
| | |
| | | |
| | | from pyramid import testing |
| | | |
| | | class PageModelTests(unittest.TestCase): |
| | | |
| | | def _getTargetClass(self): |
| | | from .models import Page |
| | | return Page |
| | | class ViewTests(unittest.TestCase): |
| | | def setUp(self): |
| | | self.config = testing.setUp() |
| | | |
| | | def _makeOne(self, data=u'some data'): |
| | | return self._getTargetClass()(data=data) |
| | | def tearDown(self): |
| | | testing.tearDown() |
| | | |
| | | def test_constructor(self): |
| | | instance = self._makeOne() |
| | | self.assertEqual(instance.data, u'some data') |
| | | |
| | | class WikiModelTests(unittest.TestCase): |
| | | |
| | | def _getTargetClass(self): |
| | | from .models import Wiki |
| | | return Wiki |
| | | |
| | | def _makeOne(self): |
| | | return self._getTargetClass()() |
| | | |
| | | def test_it(self): |
| | | wiki = self._makeOne() |
| | | self.assertEqual(wiki.__parent__, None) |
| | | self.assertEqual(wiki.__name__, None) |
| | | |
| | | class AppmakerTests(unittest.TestCase): |
| | | |
| | | def _callFUT(self, zodb_root): |
| | | from .models import appmaker |
| | | return appmaker(zodb_root) |
| | | |
| | | def test_it(self): |
| | | root = {} |
| | | self._callFUT(root) |
| | | self.assertEqual(root['app_root']['FrontPage'].data, |
| | | 'This is the front page') |
| | | |
| | | class ViewWikiTests(unittest.TestCase): |
| | | def test_it(self): |
| | | from .views import view_wiki |
| | | context = testing.DummyResource() |
| | | def test_my_view(self): |
| | | from .views import my_view |
| | | request = testing.DummyRequest() |
| | | response = view_wiki(context, request) |
| | | self.assertEqual(response.location, 'http://example.com/FrontPage') |
| | | |
| | | class ViewPageTests(unittest.TestCase): |
| | | def _callFUT(self, context, request): |
| | | from .views import view_page |
| | | return view_page(context, request) |
| | | |
| | | def test_it(self): |
| | | wiki = testing.DummyResource() |
| | | wiki['IDoExist'] = testing.DummyResource() |
| | | context = testing.DummyResource(data='Hello CruelWorld IDoExist') |
| | | context.__parent__ = wiki |
| | | context.__name__ = 'thepage' |
| | | request = testing.DummyRequest() |
| | | info = self._callFUT(context, request) |
| | | self.assertEqual(info['page'], context) |
| | | self.assertEqual( |
| | | info['content'], |
| | | '<div class="document">\n' |
| | | '<p>Hello <a href="http://example.com/add_page/CruelWorld">' |
| | | 'CruelWorld</a> ' |
| | | '<a href="http://example.com/IDoExist/">' |
| | | 'IDoExist</a>' |
| | | '</p>\n</div>\n') |
| | | self.assertEqual(info['edit_url'], |
| | | 'http://example.com/thepage/edit_page') |
| | | |
| | | |
| | | class AddPageTests(unittest.TestCase): |
| | | def _callFUT(self, context, request): |
| | | from .views import add_page |
| | | return add_page(context, request) |
| | | |
| | | def test_it_notsubmitted(self): |
| | | context = testing.DummyResource() |
| | | request = testing.DummyRequest() |
| | | request.subpath = ['AnotherPage'] |
| | | info = self._callFUT(context, request) |
| | | self.assertEqual(info['page'].data,'') |
| | | self.assertEqual( |
| | | info['save_url'], |
| | | request.resource_url(context, 'add_page', 'AnotherPage')) |
| | | |
| | | def test_it_submitted(self): |
| | | context = testing.DummyResource() |
| | | request = testing.DummyRequest({'form.submitted':True, |
| | | 'body':'Hello yo!'}) |
| | | request.subpath = ['AnotherPage'] |
| | | self._callFUT(context, request) |
| | | page = context['AnotherPage'] |
| | | self.assertEqual(page.data, 'Hello yo!') |
| | | self.assertEqual(page.__name__, 'AnotherPage') |
| | | self.assertEqual(page.__parent__, context) |
| | | |
| | | class EditPageTests(unittest.TestCase): |
| | | def _callFUT(self, context, request): |
| | | from .views import edit_page |
| | | return edit_page(context, request) |
| | | |
| | | def test_it_notsubmitted(self): |
| | | context = testing.DummyResource() |
| | | request = testing.DummyRequest() |
| | | info = self._callFUT(context, request) |
| | | self.assertEqual(info['page'], context) |
| | | self.assertEqual(info['save_url'], |
| | | request.resource_url(context, 'edit_page')) |
| | | |
| | | def test_it_submitted(self): |
| | | context = testing.DummyResource() |
| | | request = testing.DummyRequest({'form.submitted':True, |
| | | 'body':'Hello yo!'}) |
| | | response = self._callFUT(context, request) |
| | | self.assertEqual(response.location, 'http://example.com/') |
| | | self.assertEqual(context.data, 'Hello yo!') |
| | | info = my_view(request) |
| | | self.assertEqual(info['project'], 'tutorial') |
| | |
| | | view_url = request.resource_url(page) |
| | | return '<a href="%s">%s</a>' % (view_url, word) |
| | | else: |
| | | add_url = request.application_url + '/add_page/' + word |
| | | add_url = request.application_url + '/add_page/' + word |
| | | return '<a href="%s">%s</a>' % (add_url, word) |
| | | |
| | | content = publish_parts(context.data, writer_name='html')['html_body'] |
| | | content = wikiwords.sub(check, content) |
| | | edit_url = request.resource_url(context, 'edit_page') |
| | | |
| | | return dict(page = context, content = content, edit_url = edit_url, |
| | | logged_in = request.authenticated_userid) |
| | | return dict(page=context, content=content, edit_url=edit_url, |
| | | logged_in=request.authenticated_userid) |
| | | |
| | | @view_config(name='add_page', context='.models.Wiki', |
| | | renderer='templates/edit.pt', |
| | |
| | | page.__name__ = pagename |
| | | page.__parent__ = context |
| | | context[pagename] = page |
| | | return HTTPFound(location = request.resource_url(page)) |
| | | return HTTPFound(location=request.resource_url(page)) |
| | | save_url = request.resource_url(context, 'add_page', pagename) |
| | | page = Page('') |
| | | page.__name__ = pagename |
| | |
| | | def edit_page(context, request): |
| | | if 'form.submitted' in request.params: |
| | | context.data = request.params['body'] |
| | | return HTTPFound(location = request.resource_url(context)) |
| | | return HTTPFound(location=request.resource_url(context)) |
| | | |
| | | return dict(page=context, |
| | | save_url=request.resource_url(context, 'edit_page'), |
| | |
| | | login_url = request.resource_url(request.context, 'login') |
| | | referrer = request.url |
| | | if referrer == login_url: |
| | | referrer = '/' # never use the login form itself as came_from |
| | | referrer = '/' # never use the login form itself as came_from |
| | | came_from = request.params.get('came_from', referrer) |
| | | message = '' |
| | | login = '' |
| | |
| | | password = request.params['password'] |
| | | if USERS.get(login) == password: |
| | | headers = remember(request, login) |
| | | return HTTPFound(location = came_from, |
| | | headers = headers) |
| | | return HTTPFound(location=came_from, |
| | | headers=headers) |
| | | message = 'Failed login' |
| | | |
| | | return dict( |
| | | message = message, |
| | | url = request.application_url + '/login', |
| | | came_from = came_from, |
| | | login = login, |
| | | password = password, |
| | | ) |
| | | message=message, |
| | | url=request.application_url + '/login', |
| | | came_from=came_from, |
| | | login=login, |
| | | password=password, |
| | | ) |
| | | |
| | | |
| | | @view_config(context='.models.Wiki', name='logout') |
| | | def logout(request): |
| | | headers = forget(request) |
| | | return HTTPFound(location = request.resource_url(request.context), |
| | | headers = headers) |
| | | return HTTPFound(location=request.resource_url(request.context), |
| | | headers=headers) |