Steve Piercy
2016-04-10 a38b846a5aaeaad4da3a97b7ecaee086d7df729f
- update wiki authorization step and its src files
- trim index
12 files modified
248 ■■■■ changed files
docs/tutorials/wiki/authorization.rst 40 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/index.rst 1 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/CHANGES.txt 3 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/README.txt 3 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/development.ini 8 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/production.ini 6 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/setup.py 21 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/tutorial/models.py 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/tutorial/static/theme.min.css 2 ●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt 5 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/tutorial/tests.py 124 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/tutorial/views.py 33 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/authorization.rst
@@ -5,12 +5,12 @@
====================
: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
@@ -41,7 +41,7 @@
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
@@ -67,7 +67,7 @@
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
@@ -109,7 +109,7 @@
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
@@ -142,7 +142,7 @@
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
@@ -196,7 +196,7 @@
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
@@ -236,7 +236,7 @@
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
@@ -247,7 +247,7 @@
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:
@@ -274,8 +274,8 @@
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
@@ -291,7 +291,7 @@
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:
@@ -300,7 +300,7 @@
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:
@@ -309,7 +309,7 @@
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:
@@ -318,7 +318,7 @@
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
@@ -328,7 +328,7 @@
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
docs/tutorials/wiki/index.rst
@@ -26,4 +26,3 @@
   authorization
   tests
   distributing
docs/tutorials/wiki/src/authorization/CHANGES.txt
@@ -1,5 +1,4 @@
0.0
---
- Initial version
-  Initial version
docs/tutorials/wiki/src/authorization/README.txt
@@ -1,4 +1 @@
tutorial README
docs/tutorials/wiki/src/authorization/development.ini
@@ -1,6 +1,6 @@
###
# 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]
@@ -29,12 +29,12 @@
[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]
@@ -62,4 +62,4 @@
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
docs/tutorials/wiki/src/authorization/production.ini
@@ -1,6 +1,6 @@
###
# 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]
@@ -29,7 +29,7 @@
###
# 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]
@@ -57,4 +57,4 @@
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
docs/tutorials/wiki/src/authorization/setup.py
@@ -20,16 +20,22 @@
    '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='',
@@ -37,9 +43,10 @@
      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
docs/tutorials/wiki/src/authorization/tutorial/models.py
@@ -17,7 +17,7 @@
        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
docs/tutorials/wiki/src/authorization/tutorial/static/theme.min.css
@@ -1 +1 @@
@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}}
docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
@@ -34,14 +34,15 @@
          <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&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
              <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;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>
docs/tutorials/wiki/src/authorization/tutorial/tests.py
@@ -2,122 +2,16 @@
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')
docs/tutorials/wiki/src/authorization/tutorial/views.py
@@ -37,15 +37,15 @@
            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',
@@ -58,7 +58,7 @@
        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
@@ -73,7 +73,7 @@
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'),
@@ -86,7 +86,7 @@
    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 = ''
@@ -96,20 +96,21 @@
        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)