Basic Auth in Turbogears - jordy33/turbogears_tutorial GitHub Wiki

Basic Auth

Substitute app_cfg.py with the following:

from tg.configuration import AppConfig
from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin
from repoze.who.plugins.basicauth import BasicAuthPlugin

authtkt = AuthTktCookiePlugin('authtkt', 'authtkt')
basicauth = BasicAuthPlugin("something")

import myprojectname
from myprojectname import model, lib

base_config = AppConfig()
base_config.renderers = []

# True to prevent dispatcher from striping extensions
# For example /socket.io would be served by "socket_io"
# method instead of "socket".
base_config.disable_request_extensions = False

# Set None to disable escaping punctuation characters to "_"
# when dispatching methods.
# Set to a function to provide custom escaping.
base_config.dispatch_path_translator = True

base_config.prefer_toscawidgets2 = True

base_config.package = myprojectname

# Enable json in expose
base_config.renderers.append('json')

# Set the default renderer
base_config.renderers.append('mako')
base_config.renderers.append('kajiki')
base_config['templating.kajiki.strip_text'] = False  # Change this in setup.py too for i18n to work.

base_config.default_renderer = 'mako'

base_config.sa_auth.cookie_secret = "2a817308-b0ea-4ca7-bb42-00a20c941280"
# Configure Sessions, store data as JSON to avoid pickle security issues
base_config['session.enabled'] = True
base_config['session.data_serializer'] = 'json'
# Configure the base SQLALchemy Setup
base_config.use_sqlalchemy = True
base_config.model = myprojectname.model
base_config.DBSession = myprojectname.model.DBSession
# Configure the authentication backend
base_config.auth_backend = 'htpasswd'
# YOU MUST CHANGE THIS VALUE IN PRODUCTION TO SECURE YOUR APP

# what is the class you want to use to search for users in the database


from tg.configuration.auth import TGAuthMetadata


# This tells to TurboGears how to retrieve the data for your user
class ApplicationAuthMetadata(TGAuthMetadata):
    def __init__(self, sa_auth):
        self.sa_auth = sa_auth

    def get_user(self, identity, userid):
        # As we use htpasswd for authentication
        # we cannot lookup the user in a database,
        # so just return a fake user object
        from tg.util import Bunch
        return Bunch(display_name=userid, user_name=userid)

    def get_groups(self, identity, userid):
        # If the user is manager we give him the
        # managers group, otherwise no groups
        if userid == 'manager':
            return ['managers']
        else:
            return []

    def get_permissions(self, identity, userid):
        return []

base_config.sa_auth.authmetadata = ApplicationAuthMetadata(base_config.sa_auth)
from repoze.who.plugins.basicauth import BasicAuthPlugin
from repoze.who.plugins.htpasswd import HTPasswdPlugin, plain_check
base_config.sa_auth.authenticators = [('htpasswd', HTPasswdPlugin('./passwd_file', plain_check))]

# In case ApplicationAuthMetadata didn't find the user discard the whole identity.
# This might happen if logged-in users are deleted.
base_config['identity.allow_missing_user'] = False

# You can use a different repoze.who Authenticator if you want to
# change the way users can login
# base_config.sa_auth.authenticators = [('myauth', SomeAuthenticator()]

# You can add more repoze.who metadata providers to fetch
# user metadata.
# Remember to set base_config.sa_auth.authmetadata to None
# to disable authmetadata and use only your own metadata providers
# base_config.sa_auth.mdproviders = [('myprovider', SomeMDProvider()]

# override this if you would like to provide a different who plugin for
# managing login and logout of your application


# You may optionally define a page where you want users to be redirected to
# on login:


# You may optionally define a page where you want users to be redirected to
# on logout:
base_auth = BasicAuthPlugin('MyTGApp')

base_config.sa_auth.identifiers = [('basicauth', base_auth)]

base_config.sa_auth.form_identifies = False
base_config.sa_auth.challengers = [('basicauth', base_auth)]



try:
    # Enable DebugBar if available, install tgext.debugbar to turn it on
    from tgext.debugbar import enable_debugbar
    enable_debugbar(base_config)
except ImportError:
    pass

In a different named project you should go to app_cfg.py and change myprojectname with whatever is your project named. In your project directory you must include the file with passwords. With vim or nano create a file named:passwd_file Insert the following:

manager:managepass

Add all the users that you need to authenticate. Edit pets.py controller and sustitute with the following

# -*- coding: utf-8 -*-
"""The base Controller API."""
from tg import RestController
from tg.decorators import expose
from tg import predicates

__all__ = ['PetsController']


class PetsController(RestController):
    allow_only = predicates.not_anonymous()
    @expose('json')
    def get_all(self):
        return dict(name='Boony',breed='Dog')

    @expose('json')
    def post(self, name,breed):
        print("POST Name: {} Breed: {}".format(name,breed))
        return dict(name=name,breed=breed)

    @expose('json')
    def put(self, name,breed):
        print("PUT Name: {} Breed: {}".format(name,breed))
        return dict(name=name,breed=breed)
    
    @expose('json')
    def post_delete(self, name,breed):
        print("DELETE Name: {} Breed: {}".format(name,breed))
        return dict(name=name,breed=breed)

Add to root controller at the top

from myprojectname.controllers.pets import PetsController

Add to root controller bellow class RootController

pets = PetsController()

You can check another authentication here

Test the code:

http://localhost:8080/pets

Result

Try in the Pycharm console or the python REPL:

import requests
r=requests.get("http://localhost:8080/pets")
print(r)
<Response [401]>
print r.text
<html>
 <head>
  <title>401 Unauthorized</title>
 </head>
 <body>
  <h1>401 Unauthorized</h1>
  This server could not verify that you are authorized to access the document you requested. Either you supplied the wrong credentials (e.g., bad password), or your browser does not understand how to supply the credentials required.<br /><br />
 </body>
</html>

Now check with credentials, using the following code:

import
r=requests.get("http://localhost:8080/pets", auth=("manager", "managepass"))
print (r)
<Response [200]>
print r.json()
{u'breed': u'Dog', u'name': u'Boony'}

Inside the RootController put the following

class RootController(BaseController):

    """
    The root controller for the myprojectname application.

    All the other controllers and WSGI applications should be mounted on this
    controller. For example::

        panel = ControlPanelController()
        another_app = AnotherWSGIApplication()

    Keep in mind that WSGI applications shouldn't be mounted directly: They
    must be wrapped around with :class:`tg.controllers.WSGIAppController`.

    """
    secc = SecureController()
    admin = AdminController(model, DBSession, config_type=TGAdminConfig)
    pets = PetsController()
    error = ErrorController()

    def _before(self, *args, **kw):
        tmpl_context.project_name = "myprojectname"

    @expose('myprojectname.templates.index')
    def index(self):
        """Handle the front-page."""
        print("index")
        return dict(page='index')

    @expose('myprojectname.templates.index')
    @require(predicates.has_permission('manage', msg=l_('Only for managers')))
    def manage_permission_only(self, **kw):
        """Illustrate how a page for managers only works."""
        return dict(page='managers stuff')

    @expose('myprojectname.templates.index')
    @require(predicates.is_user('editor', msg=l_('Only for the editor')))
    def editor_user_only(self, **kw):
        """Illustrate how a page exclusive for the editor works."""
        return dict(page='editor stuff')

    @expose()
    def check(self):
        print("Check")
        return "UP"

    @expose('myprojectname.templates.main')
    def main(self):
        """Handle the front-page."""
        print("main")
        return dict(page='index')

Add main.mak

<!DOCTYPE html>

<html lang="en">
<head>

    <link href="${tg.url('/lib/css/bootstrap.min.css')}" rel="stylesheet">

    <style type="text/css">
          /* NOTE: The styles were added inline because Prefixfree needs access to your styles and they must be inlined if they are on local disk! */


body {
  background: #999;
  padding: 40px;
  font-family: "Open Sans Condensed", sans-serif;
}
h1 {
  color: white;
}
#bg {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: url(${tg.url('/img/clean-back.png')}) no-repeat center center fixed;
  background-size: cover;
  -webkit-filter: blur(0px);
}

form {
  position: relative;
  width: 250px;
  margin: 0 auto;
  background: rgba(130,130,130,.3);
  padding: 20px 22px;
  border: 1px solid;
  border-top-color: rgba(255,255,255,.4);
  border-left-color: rgba(255,255,255,.4);
  border-bottom-color: rgba(60,60,60,.4);
  border-right-color: rgba(60,60,60,.4);
}

form input, form button {
  width: 212px;
  border: 1px solid;
  border-bottom-color: rgba(255,255,255,.5);
  border-right-color: rgba(60,60,60,.35);
  border-top-color: rgba(60,60,60,.35);
  border-left-color: rgba(80,80,80,.45);
  background-color: rgba(0,0,0,1); /* antes .2 */
  background-repeat: no-repeat;
  padding: 8px 24px 8px 10px;
  font: bold .875em/1.25em "Open Sans Condensed", sans-serif;
  letter-spacing: .075em;
  color: #fff;
  text-shadow: 0 1px 0 rgba(0,0,0,.1);
  margin-bottom: 19px;
}

form input:focus { background-color: rgba(0,0,0,.4); }

::-webkit-input-placeholder { color: #ccc; text-transform: uppercase; }
::-moz-placeholder { color: #ccc; text-transform: uppercase; }
:-ms-input-placeholder { color: #ccc; text-transform: uppercase; }

form button[type=submit] {
  width: 210px;
  margin-bottom: 0;
  color: rgba(58, 131, 214, 0.76);
  letter-spacing: .05em;
  text-shadow: 0 1px 0 #133d3e;
  text-transform: uppercase;
  background: #d0d2d2;
  border-top-color: #9fb5b5;
  border-left-color: #608586;
  border-bottom-color: #1b4849;
  border-right-color: #1e4d4e;
  cursor: pointer;
}
    </style>
    <script type="text/javascript">


    </script>
</head>
<body>
  <div id="bg"></div>
  <br>

  <br><br><br><br><br><br><br>

<form action="${tg.url('/secc/root')}" method="post" accept-charset="UTF-8">

<h1><center>My project</center></h1>
<br>
  <br>

  <button id="enterButton" type="submit">Enter</button>

</form>
</body>

Update master.mak

<!DOCTYPE html>
<html>
<head>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBWs-R7lvNo7pooz56iQOqrfQXq0BbIfgo"
async defer></script>
    ${self.meta()}
    <title>${self.title()}</title>

    <link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/css/bootstrap.min.css')}" />
    <link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/css/style.css')}" />
    <script src="http://code.jquery.com/jquery.js"></script>
    <script src="${tg.url('/javascript/bootstrap.min.js')}"></script>
    <!-- Global JavaScript -->
    <script type="text/javascript">
    function shake(){
        alert('Shake');
    }

    function shrink(){
        alert('Shrink');
    }
    function logout() {
            var i = document.childNodes.length - 1;
            while (i >= 0) {
              console.log(document.childNodes[i]);
              document.removeChild(document.childNodes[i--]);
            }
            jQuery.ajax({
                    type: "GET",
                    url: "/secc",
                    username: "logmeout",
                    password: "123456",
                    headers: { "Authorization": "Basic xxx" }
            })
            .done(function(){
                // If we don't get an error, we actually got an error as we expect an 401!
            })
            .fail(function(){
                // We expect to get an 401 Unauthorized error! In this case we are successfully
                    // logged out and we redirect the user.
                window.location = "/";
            });

            return false;
    }
    </script>
    ${self.head_content()}
</head>

% if tg.auth_stack_enabled:
  % if not request.identity:
       <meta http-equiv="refresh" content="0; url=${tg.url('/main')}" />

  % else:
        <body class="${self.body_class()}">
          ${self.main_menu()}
          ${self.footer()}
        <!-- Insert nice scrool -->
          ${self.bottom_scripts()}
  % endif
% endif

<%def name="meta()">
  <meta charset="${response.charset}" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</%def>

<%def name="title()">  </%def>

<%def name="head_content()"></%def>

<%def name="body_class()"></%def>

<%def name="main_menu()">
  <!-- Navbar -->
  <nav class="navbar navbar-default">
    <div class="navbar-header">

      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-content">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="${tg.url('/')}">
        <p>User:${request.identity['repoze.who.userid']}</p>
      </a>
    </div>

    <div class="collapse navbar-collapse" id="navbar-content">
      <ul class="nav navbar-nav">
        <li class="${('', 'active')[page=='index']}"><a href="${tg.url('/')}">Welcome</a></li>
        <li class="${('', 'active')[page=='about']}"><a href="${tg.url('/about')}">About</a></li>
        <li class="${('', 'active')[page=='data']}"><a href="${tg.url('/data')}">Serving Data</a></li>
        <li class="${('', 'active')[page=='environ']}"><a href="${tg.url('/environ')}">WSGI Environment</a></li>
         %if request.identity['repoze.who.userid']=='manager':
              <li class="${('', 'active')[page=='admin']}"><a href="${tg.url('/admin')}">ADMIN</a></li>
         % endif
      </ul>

    % if tg.auth_stack_enabled:
      <ul class="nav navbar-nav navbar-right">
      % if request.identity:
        <li><a href="#" onclick=logout();>Logout</a></li>
      % endif
      </ul>
    % endif
    </div>
  </nav>
  ${self.content_wrapper()}
</%def>

</html>




<%def name="content_wrapper()">
  <%
    flash=tg.flash_obj.render('flash', use_js=False)
  %>
  % if flash:
      <div class="row">
        <div class="col-md-8 col-md-offset-2">
              ${flash | n}
        </div>
      </div>
  % endif
  ${self.body()}
</%def>

<%def name="footer()">
  <footer class="footer hidden-xs hidden-sm">
    <a class="pull-right" href="http://www.turbogears.org"><img style="vertical-align:middle;" src="${tg.url('/img/under_the_hood_blue.png')}" alt="TurboGears 2" /></a>
    <p>Copyright &copy; ${getattr(tmpl_context, 'project_name', 'TurboGears2')} ${h.current_year()}</p>
  </footer>
</%def>

<%def name="bottom_scripts()">

 <script>
 </script>
</%def>

Update auth.py def _set_password with the following:

    def _set_password(self, password):
        """Hash ``password`` on the fly and store its hashed version."""
        self._password = self._hash_password(password)
        print("User NAME:{}".format(self.user_name))
        with open('passwd_file') as fin:
            file_str = fin.read()
        startPosition=file_str.find(self.user_name+':')
        print("Start position:{}".format(startPosition))
        if startPosition>-1:
            endPosition=file_str.index('\n',19)
            print("End position:{}".format(endPosition))
            keyword=file_str[startPosition:endPosition+1]
            file_str = file_str.replace(keyword, '')
            print("Contents:{}".format(repr(file_str)))
        file_str= file_str + self.user_name+':'+password+'\n'
        with open('passwd_file', "w") as f:
            f.write(file_str)

Modify secure.py controller with the following:

# -*- coding: utf-8 -*-
"""Sample controller with all its actions protected."""
from tg import expose, flash
from tg.i18n import ugettext as _, lazy_ugettext as l_
from tg import predicates

from myprojectname.lib.base import BaseController

__all__ = ['SecureController']


class SecureController(BaseController):
    """Sample controller-wide authorization"""

    # The predicate that must be met for all the actions in this controller:
    # allow_only = has_permission(
    #     'manage',
    #     msg=l_('Only for people with the "manage" permission')
    # )
    allow_only = predicates.not_anonymous()
    @expose('myprojectname.templates.index')
    def root(self):
        """Let the user know that's visiting a protected controller."""
        #flash(_("Secure Controller here"))
        return dict(page='index')

Try basic authentication

http://localhost:8080
⚠️ **GitHub.com Fallback** ⚠️