Showing posts with label python. Show all posts
Showing posts with label python. Show all posts

2016-08-04

setting up basic Django authentication

So one of the things that Django does not currently do (as of 1.10) is provide a built-in login screen template for your app (like web2py does), even though it provides the views and forms as part of django.contrib.auth, but that's most probably because of its use in the admin app. So here's how you'd setup a basic one, using the built-in views:

First, the default LOGIN_URL is the urlpath /accounts/login, which you can see in your Django's django/conf/global_settings.py. You can switch your project to use a different one. In django/contrib/auth/views.py, we notice that the login() defaults its template_name to 'registration/login.html'. You can switch this in your project's URL conf. In any case, if we stick to the defaults we need to add the following to our project's URL conf in myproject/urls.py:

from django.contrib.auth.views import login, logout

urlpatterns = [
    # the other urlpatterns for your project
    url(r'^accounts/login/$', login),
    url(r'^accounts/logout/$', logout),
]

Next, assuming that in myproject/settings.py, APP_DIRS is True, then we need to build in the page template for the form at myproject/myapp/templates/registration/login.html:

{% extends "registration/base.html" %}

{% block content %}

  <div class="container">

    {% if form.errors and not form.non_field_errors %}
      <p class="errornote">
      {% if form.errors.items|length == 1 %}
        "Please correct the error below."
      {% else %}
        "Please correct the errors below."
      {% endif %}
      </p>
    {% endif %}

    {% if form.non_field_errors %}
      {% for error in form.non_field_errors %}
        <p class="errornote">
        {{ error }}
        </p>
      {% endfor %}
    {% endif %}

    <form class="form-signin" method="POST">
      {% csrf_token %}
      <h2 class="form-signin-heading">Please sign in</h2>
      <label for="inputUser" class="sr-only">User Name</label>
      <input type="text" name="username" id="inputUser" class="form-control" placeholder="User Name" maxlength="151" required autofocus>
      <label for="inputPassword" class="sr-only">Password</label>
      <input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
      <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
    </form>
  
  </div>

{% endblock %}

NOTE: Because the builtin-view login is called, which renders the builtin AuthenticationForm, you don't specify a form action in your login form template. Doing so will result in a Error NoReverseMatch on Reverse Django URL!

Then in the view that you want to protect:

from django.http import HttpResponse
from django.contrib.auth.decorators import login_required

@login_required
def index(request):
    # yadda
    return HttpResponse(yourviewtemplate.render(Context()))

Then when you go to the index view, you get a screen that looks like:

Note that the base.html references bootstrap.css and friends

auto register all Django models in admin

In Django admin if you want to autoload all app models in admin without having to enumerate each one:

Instead of what the tutorial writes:

from django.contrib import admin
from .models import Publisher, Author, Book

admin.site.register(Publisher)
admin.site.register(Author)
admin.site.register(Book)

instead:

from django.contrib import admin
from . import models
import inspect

for name, obj in inspect.getmembers(models):
    if inspect.isclass(obj):
        admin.site.register(getattr(models, name))

2016-08-01

FreeBSD virtualenv (for things like django)

Useful: http://kdebowski.pl/blog/freebsd-nginx-gunicorn-django-virtualenv-mysql/

System Era: FreeBSD 10.3
PYTHON_VERSION=python3.4

Remember to install databases/py-sqlite3 !

KEY THING after you've installed devel/py-virtualenv:

% virtualenv-3.4 --no-site-packages path_to_virtualenv/virtualenv_name
% source path_to_virtualenv/virtualenv_name/bin/activate.csh

Now you're in the virtualenv and you can pip and so forth.

2015-05-04

Read file pattern (Python)

Here's a pattern that I've been using a lot to read files in Python 2.7:
import codecs
badrow_count = 0
goodrow_count = 0
rowcount = 0

filename = "somefile.txt"
encoding = "utf-8"
# see https://docs.python.org/2/library/codecs.html#standard-encodings
# for encodings

decode_error_handler = 'strict'
# this is the default
# see https://docs.python.org/2/library/codecs.html#codec-base-classes 
# for decoding error callbacks

f = codecs.open(filename=filename, mode='rU', encoding=encoding, 
    errors=decode_error_handler)
eof = False
while not eof:
    row = u''
    try:
        row = f.next()
    except UnicodeDecodeError as e:
        badrow_count += 1
        # do other things on this row
    except StopIteration:
        eof = True
    #except Exception as e:
        # handle other issues    
    else:
        goodrow_count += 1
        # do other stuff with row
    finally:
        if not eof:
            rowcounter += 1
        else:
            break
I prefer this to:
for row in f:
primarily in order to catch unicode decoder errors.

2014-06-30

Auth.user itself is a DAL row object

So after we instantiate auth=Auth(db=db), when the user logs in, Auth will create a subclass of type <class 'gluon.dal.Row'> which stores the fields from auth_user table:

email
first_name
id
last_name
registration_id
registration_key
reset_password_key
username

Locking down a default web2py controller

Say you have a web2py app where you want the default controller default.py to be locked down to require a user to always be authenticated to access any of the functions in that controller.

Problem: by default, user():, which calls auth() to supply the default login form handler is in default.py so if use the typical method to force auth checking on the controller itself (as explained in https://groups.google.com/d/msg/web2py/ReznbEX0Mh0/CfyEF70TrG0J)
auth.requires_login()(lambda: None)()

def index():
    return dict()

def user():
    return dict(form=auth())
This will result in a redirection loop because the initial un-logged-in access to /app/(default/index) will result in a redirect (due to requires_login()) to /app/default/user/login and on every call to default will result in a redirect to default/user/login etc.

So,we have to move user() out of the way.

To do this, we have to tell Auth where to find user(). By default it looks in default controller, but this can be modified in the Auth instantiation in the model (models/db.py):
#original auth instance
#auth = Auth(db)

#redirected auth instance
auth = Auth(db=db, controller='login')
This tells Auth instance auth to look in controller login for the user() instead of default. We use keyword db for telling it which DAL instance to use now, since we have switched to kwargs. In controllers/login.py:
# coding: utf8

#here is the default action, redirect back to the default controller
def index():
    redirect(URL(c='default'))

def user():
    return dict(form=auth())
And don't forget to move views/default/user.html to views/login/user.html!

2014-06-29

Web2py Auth User against LDAP (Active Directory)

In models/db.py, adapted from the example one from welcome:
#using the default DAL db. You can use pg if you want
db = DAL('sqlite://storage.sqlite',pool_size=1,check_reserved=['all'])

#store sessions in the db not on the filesystem
session.connect(request, response, db=db)

#default boilerplate from welcome
response.generic_patterns = ['*'] if request.is_local else []

#default boilerplate from welcome
from gluon.tools import Auth, Crud, Service, PluginManager, prettydate
auth = Auth(db)
crud, service, plugins = Crud(db), Service(), PluginManager()

#use username as the primary id, not email address
auth.define_tables(username=True, signature=False)

#do not create a default user group (=user) for every user that gets imported
auth.settings.create_user_groups=False

#default config from welcome
mail = auth.settings.mailer
mail.settings.server = 'logging' or 'smtp.gmail.com:587'
mail.settings.sender = 'you@gmail.com'
mail.settings.login = 'username:password'

#comment these from the default
#auth.settings.registration_requires_verification = False
#auth.settings.registration_requires_approval = False
#auth.settings.reset_password_requires_verification = True

#LDAP is always the system of record, so disable manual registration or the changing of the user in the app
auth.settings.actions_disabled=['register','change_password','request_reset_password','retrieve_username','profile']

#this is just good security
auth.settings.remember_me_form = False

#import ldap_auth method
from gluon.contrib.login_methods.ldap_auth import ldap_auth

#override all/any default auth settings, users can *only* auth against Active Directory
auth.settings.login_methods=[ldap_auth(mode='ad',
                                       manage_user=True,
                                       user_firstname_attrib = 'givenName',
                                       user_lastname_attrib = 'sn',
                                       user_mail_attrib = 'mail',
                                       server='corp.contoso.com',
                                       base_dn='dc=contoso,dc=com',
                                       secure=True,
                                       db=db)]

#disable janrain
#from gluon.contrib.login_methods.rpx_account import use_janrain
#use_janrain(auth, filename='private/janrain.key')

Usage: The Login dialog will cause this web2py app to autocreate a user based on the attributes in LDAP. The actual auth is the return of a successful LDAP bind. You can also pre-create users using appadmin. When manually creating users this way, you will need to set a dummy password in the db since it is set to be a required field (but will remain empty when the user is autocreated...). You may want to manually add users when you are setting up app-specific groups.

Caveats: To get LDAP secure=True working with a self-signed cert on the webserver, I had to hack gluon/contrib/login_methods/ldap_auth.py: In ldap_auth().init_ldap(), I had to add the following after if secure:

ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)

See also: http://www.web2pyslices.com/slice/show/1715/authentication-and-group-control-with-active-directory-ldap if you want to base RBAC off AD groups.