Changeset - 98abf8953b87
[Not reviewed]
default
0 4 0
Marcin Kuzminski - 15 years ago 2010-07-24 02:18:48
marcin@python-works.com
Added user registration, changed login url schema, moved it into _admin/ for safety
4 files changed with 52 insertions and 11 deletions:
0 comments (0 inline, 0 general)
pylons_app/config/routing.py
Show inline comments
 
"""Routes configuration
 

	
 
The more specific and detailed routes should be defined first so they
 
may take precedent over the more generic routes. For more information
 
refer to the routes manual at http://routes.groovie.org/docs/
 
"""
 
from routes import Mapper
 
from pylons_app.lib.utils import check_repo_fast as cr
 

	
 
def make_map(config):
 
    """Create, configure and return the routes Mapper"""
 
    map = Mapper(directory=config['pylons.paths']['controllers'],
 
                 always_scan=config['debug'])
 
    map.minimization = False
 
    map.explicit = False
 

	
 
    # The ErrorController route (handles 404/500 error pages); it should
 
    # likely stay at the top, ensuring it can always be resolved
 
    map.connect('/error/{action}', controller='error')
 
    map.connect('/error/{action}/{id}', controller='error')
 

	
 
    # CUSTOM ROUTES HERE
 
    map.connect('hg_home', '/', controller='hg', action='index')
 
    
 
    def check_repo(environ, match_dict):
 
        """
 
        check for valid repository for proper 404 handling
 
        @param environ:
 
        @param match_dict:
 
        """
 
        repo_name = match_dict.get('repo_name')
 
        return not cr(repo_name, config['base_path'])
 
 
 
    #REST routes
 
    with map.submapper(path_prefix='/_admin', controller='admin/repos') as m:
 
        m.connect("repos", "/repos",
 
             action="create", conditions=dict(method=["POST"]))
 
        m.connect("repos", "/repos",
 
             action="index", conditions=dict(method=["GET"]))
 
        m.connect("formatted_repos", "/repos.{format}",
 
             action="index",
 
            conditions=dict(method=["GET"]))
 
        m.connect("new_repo", "/repos/new",
 
             action="new", conditions=dict(method=["GET"]))
 
        m.connect("formatted_new_repo", "/repos/new.{format}",
 
             action="new", conditions=dict(method=["GET"]))
 
        m.connect("/repos/{repo_name:.*}",
 
             action="update", conditions=dict(method=["PUT"],
 
                                              function=check_repo))
 
        m.connect("/repos/{repo_name:.*}",
 
             action="delete", conditions=dict(method=["DELETE"],
 
                                              function=check_repo))
 
        m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
 
             action="edit", conditions=dict(method=["GET"],
 
                                            function=check_repo))
 
        m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
 
             action="edit", conditions=dict(method=["GET"],
 
                                            function=check_repo))
 
        m.connect("repo", "/repos/{repo_name:.*}",
 
             action="show", conditions=dict(method=["GET"],
 
                                            function=check_repo))
 
        m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
 
             action="show", conditions=dict(method=["GET"],
 
                                            function=check_repo))
 
        #ajax delete repo perm user
 
        m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
 
             action="delete_perm_user", conditions=dict(method=["DELETE"],
 
                                                        function=check_repo))
 
        
 
    map.resource('user', 'users', controller='admin/users', path_prefix='/_admin')
 
    map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
 
    map.resource('setting', 'settings', controller='admin/settings', path_prefix='/_admin', name_prefix='admin_')
 
    
 
    #ADMIN
 
    with map.submapper(path_prefix='/_admin', controller='admin/admin') as m:
 
        m.connect('admin_home', '', action='index')#main page
 
        m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
 
                  action='add_repo')
 
    
 
    #LOGIN/LOGOUT
 
    map.connect('login_home', '/_admin/login', controller='login')
 
    map.connect('logout_home', '/_admin/logout', controller='login', action='logout')
 
    map.connect('register', '/_admin/register', controller='login', action='register')
 
        
 
    #FEEDS
 
    map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
 
                controller='feed', action='rss',
 
                conditions=dict(function=check_repo))
 
    map.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
 
                controller='feed', action='atom',
 
                conditions=dict(function=check_repo))
 
    
 
    #LOGIN/LOGOUT
 
    map.connect('login_home', '/login', controller='login')
 
    map.connect('logout_home', '/logout', controller='login', action='logout')
 
    
 
    #OTHERS
 
    map.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
 
                controller='changeset', revision='tip',
 
                conditions=dict(function=check_repo))
 
    map.connect('summary_home', '/{repo_name:.*}/summary',
 
                controller='summary', conditions=dict(function=check_repo))
 
    map.connect('shortlog_home', '/{repo_name:.*}/shortlog',
 
                controller='shortlog', conditions=dict(function=check_repo))
 
    map.connect('branches_home', '/{repo_name:.*}/branches',
 
                controller='branches', conditions=dict(function=check_repo))
 
    map.connect('tags_home', '/{repo_name:.*}/tags',
 
                controller='tags', conditions=dict(function=check_repo))
 
    map.connect('changelog_home', '/{repo_name:.*}/changelog',
 
                controller='changelog', conditions=dict(function=check_repo))    
 
    map.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
 
                controller='files', revision='tip', f_path='',
 
                conditions=dict(function=check_repo))
 
    map.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
 
                controller='files', action='diff', revision='tip', f_path='',
 
                conditions=dict(function=check_repo))
 
    map.connect('files_raw_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
 
                controller='files', action='rawfile', revision='tip', f_path='',
 
                conditions=dict(function=check_repo))
 
    map.connect('files_annotate_home', '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
 
                controller='files', action='annotate', revision='tip', f_path='',
 
                conditions=dict(function=check_repo))    
 
    map.connect('files_archive_home', '/{repo_name:.*}/archive/{revision}/{fileformat}',
 
                controller='files', action='archivefile', revision='tip',
 
                conditions=dict(function=check_repo))
 
    map.connect('repo_settings_update', '/{repo_name:.*}/settings',
 
                controller='settings', action="update",
 
                conditions=dict(method=["PUT"], function=check_repo))
 
    map.connect('repo_settings_home', '/{repo_name:.*}/settings',
 
                controller='settings', action='index',
 
                conditions=dict(function=check_repo))
 

	
 
    
 
    return map
pylons_app/controllers/login.py
Show inline comments
 
#!/usr/bin/env python
 
# encoding: utf-8
 
# login controller for pylons
 
# Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
 
# 
 
# This program is free software; you can redistribute it and/or
 
# modify it under the terms of the GNU General Public License
 
# as published by the Free Software Foundation; version 2
 
# of the License or (at your opinion) any later version of the license.
 
# 
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
# 
 
# You should have received a copy of the GNU General Public License
 
# along with this program; if not, write to the Free Software
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
# MA  02110-1301, USA.
 
from formencode import htmlfill
 
from pylons import request, response, session, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons_app.lib.auth import AuthUser
 
from pylons_app.lib.base import BaseController, render
 
from pylons_app.model.forms import LoginForm, RegisterForm
 
from pylons_app.model.user_model import UserModel
 
import formencode
 
import logging
 
"""
 
Created on April 22, 2010
 
login controller for pylons
 
@author: marcink
 
"""
 
import logging
 
from formencode import htmlfill
 
from pylons import request, response, session, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons_app.lib.base import BaseController, render
 
import formencode
 
from pylons_app.model.forms import LoginForm
 
from pylons_app.lib.auth import AuthUser
 

	
 
log = logging.getLogger(__name__)
 

	
 
class LoginController(BaseController):
 

	
 
    def __before__(self):
 
        super(LoginController, self).__before__()
 

	
 
    def index(self):
 
        #redirect if already logged in
 
        if c.hg_app_user.is_authenticated:
 
            return redirect(url('hg_home'))
 
        
 
        if request.POST:
 
            #import Login Form validator class
 
            login_form = LoginForm()
 
            try:
 
                c.form_result = login_form.to_python(dict(request.POST))
 
                return redirect(url('hg_home'))
 
                               
 
            except formencode.Invalid as errors:
 
                return htmlfill.render(
 
                    render('/login.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8")
 
                        
 
        return render('/login.html')
 
    
 
    
 
    def register(self):
 
        if request.POST:
 
            user_model = UserModel()
 
            register_form = RegisterForm()()
 
            try:
 
                form_result = register_form.to_python(dict(request.POST))
 
                user_model.create_registration(form_result)
 
                return redirect(url('login_home'))
 
                               
 
            except formencode.Invalid as errors:
 
                return htmlfill.render(
 
                    render('/register.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8")
 
        
 
        return render('/register.html')
 
    
 
    def logout(self):
 
        session['hg_app_user'] = AuthUser()
 
        session.save()
 
        log.info('Logging out and setting user as Empty')
 
        redirect(url('hg_home'))
pylons_app/model/forms.py
Show inline comments
 
@@ -76,229 +76,232 @@ def ValidUsername(edit, old_data):
 
class ValidPassword(formencode.validators.FancyValidator):
 
    
 
    def to_python(self, value, state):
 
        if value:
 
            return get_crypt_password(value)
 
        
 
class ValidAuth(formencode.validators.FancyValidator):
 
    messages = {
 
            'invalid_password':_('invalid password'),
 
            'invalid_login':_('invalid user name'),
 
            'disabled_account':_('Your acccount is disabled')
 
            
 
            }
 
    #error mapping
 
    e_dict = {'username':messages['invalid_login'],
 
              'password':messages['invalid_password']}
 
    e_dict_disable = {'username':messages['disabled_account']}
 
    
 
    def validate_python(self, value, state):
 
        sa = meta.Session
 
        crypted_passwd = get_crypt_password(value['password'])
 
        username = value['username']
 
        try:
 
            user = sa.query(User).filter(User.username == username).one()
 
        except (NoResultFound, MultipleResultsFound, OperationalError) as e:
 
            log.error(e)
 
            user = None
 
            raise formencode.Invalid(self.message('invalid_password',
 
                                     state=State_obj), value, state,
 
                                     error_dict=self.e_dict)            
 
        if user:
 
            if user.active:
 
                if user.username == username and user.password == crypted_passwd:
 
                    from pylons_app.lib.auth import AuthUser
 
                    auth_user = AuthUser()
 
                    auth_user.username = username
 
                    auth_user.is_authenticated = True
 
                    auth_user.is_admin = user.admin
 
                    auth_user.user_id = user.user_id
 
                    auth_user.name = user.name
 
                    auth_user.lastname = user.lastname
 
                    session['hg_app_user'] = auth_user
 
                    session.save()
 
                    log.info('user %s is now authenticated', username)
 
                    
 
                    try:
 
                        user.last_login = datetime.datetime.now()
 
                        sa.add(user)
 
                        sa.commit()                        
 
                    except (OperationalError) as e:
 
                        log.error(e)
 
                        sa.rollback()
 
                    
 
                    return value
 
                else:
 
                    log.warning('user %s not authenticated', username)
 
                    raise formencode.Invalid(self.message('invalid_password',
 
                                             state=State_obj), value, state,
 
                                             error_dict=self.e_dict)
 
            else:
 
                log.warning('user %s is disabled', username)
 
                raise formencode.Invalid(self.message('disabled_account',
 
                                         state=State_obj),
 
                                         value, state,
 
                                         error_dict=self.e_dict_disable)
 
            
 
        meta.Session.remove()
 

	
 
                   
 
class ValidRepoUser(formencode.validators.FancyValidator):
 
            
 
    def to_python(self, value, state):
 
        sa = meta.Session
 
        try:
 
            self.user_db = sa.query(User)\
 
                .filter(User.active == True)\
 
                .filter(User.username == value).one()
 
        except Exception:
 
            raise formencode.Invalid(_('This username is not valid'),
 
                                     value, state)
 
        meta.Session.remove()            
 
        return self.user_db.user_id
 

	
 
def ValidRepoName(edit, old_data):    
 
    class _ValidRepoName(formencode.validators.FancyValidator):
 
            
 
        def to_python(self, value, state):
 
            slug = h.repo_name_slug(value)
 
            if slug in ['_admin']:
 
                raise formencode.Invalid(_('This repository name is disallowed'),
 
                                         value, state)
 
            
 
            if old_data.get('repo_name') != value or not edit:    
 
                sa = meta.Session
 
                if sa.query(Repository).get(slug):
 
                    raise formencode.Invalid(_('This repository already exists') ,
 
                                             value, state)
 
                meta.Session.remove()
 
            return slug 
 
        
 
        
 
    return _ValidRepoName
 

	
 
class ValidPerms(formencode.validators.FancyValidator):
 
    messages = {'perm_new_user_name':_('This username is not valid')}
 
    
 
    def to_python(self, value, state):
 
        perms_update = []
 
        perms_new = []
 
        #build a list of permission to update and new permission to create
 
        for k, v in value.items():
 
            if k.startswith('perm_'):
 
                if  k.startswith('perm_new_user'):
 
                    new_perm = value.get('perm_new_user', False)
 
                    new_user = value.get('perm_new_user_name', False)
 
                    if new_user and new_perm:
 
                        if (new_user, new_perm) not in perms_new:
 
                            perms_new.append((new_user, new_perm))
 
                else:
 
                    usr = k[5:]                    
 
                    if usr == 'default':
 
                        if value['private']:
 
                            #set none for default when updating to private repo
 
                            v = 'repository.none'
 
                    perms_update.append((usr, v))
 
        value['perms_updates'] = perms_update
 
        value['perms_new'] = perms_new
 
        sa = meta.Session
 
        for k, v in perms_new:
 
            try:
 
                self.user_db = sa.query(User)\
 
                    .filter(User.active == True)\
 
                    .filter(User.username == k).one()
 
            except Exception:
 
                msg = self.message('perm_new_user_name',
 
                                     state=State_obj)
 
                raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})            
 
        return value
 
    
 
class ValidSettings(formencode.validators.FancyValidator):
 
    
 
    def to_python(self, value, state):
 
        #settings  form can't edit user
 
        if value.has_key('user'):
 
            del['value']['user']
 
        
 
        return value                
 
#===============================================================================
 
# FORMS        
 
#===============================================================================
 
class LoginForm(formencode.Schema):
 
    allow_extra_fields = True
 
    filter_extra_fields = True
 
    username = UnicodeString(
 
                             strip=True,
 
                             min=3,
 
                             not_empty=True,
 
                             messages={
 
                                       'empty':_('Please enter a login'),
 
                                       'tooShort':_('Enter a value %(min)i characters long or more')}
 
                            )
 

	
 
    password = UnicodeString(
 
                            strip=True,
 
                            min=3,
 
                            not_empty=True,
 
                            messages={
 
                                      'empty':_('Please enter a password'),
 
                                      'tooShort':_('Enter a value %(min)i characters long or more')}
 
                                )
 

	
 

	
 
    #chained validators have access to all data
 
    chained_validators = [ValidAuth]
 
    
 
def UserForm(edit=False, old_data={}):
 
    class _UserForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername(edit, old_data))
 
        if edit:
 
            new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
 
            admin = StringBoolean(if_missing=False)
 
        else:
 
            password = All(UnicodeString(strip=True, min=8, not_empty=True), ValidPassword)
 
        active = StringBoolean(if_missing=False)
 
        name = UnicodeString(strip=True, min=3, not_empty=True)
 
        lastname = UnicodeString(strip=True, min=3, not_empty=True)
 
        email = Email(not_empty=True)
 
        
 
    return _UserForm
 

	
 
RegisterForm = UserForm
 
    
 
    
 
def RepoForm(edit=False, old_data={}):
 
    class _RepoForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
 
        description = UnicodeString(strip=True, min=3, not_empty=True)
 
        private = StringBoolean(if_missing=False)
 
        
 
        if edit:
 
            user = All(Int(not_empty=True), ValidRepoUser)
 
        
 
        chained_validators = [ValidPerms]
 
    return _RepoForm
 

	
 
def RepoSettingsForm(edit=False, old_data={}):
 
    class _RepoForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
 
        description = UnicodeString(strip=True, min=3, not_empty=True)
 
        private = StringBoolean(if_missing=False)
 
        
 
        chained_validators = [ValidPerms, ValidSettings]
 
    return _RepoForm
 

	
 

	
 
def ApplicationSettingsForm():
 
    class _ApplicationSettingsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        app_title = UnicodeString(strip=True, min=3, not_empty=True)
 
        app_auth_realm = UnicodeString(strip=True, min=3, not_empty=True)
 
        
 
    return _ApplicationSettingsForm
 
 
 

	
 

	
pylons_app/model/user_model.py
Show inline comments
 
#!/usr/bin/env python
 
# encoding: utf-8
 
# Model for users
 
# Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
 
 
 
# This program is free software; you can redistribute it and/or
 
# modify it under the terms of the GNU General Public License
 
# as published by the Free Software Foundation; version 2
 
# of the License or (at your opinion) any later version of the license.
 
# 
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
# 
 
# You should have received a copy of the GNU General Public License
 
# along with this program; if not, write to the Free Software
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
# MA  02110-1301, USA.
 

	
 
"""
 
Created on April 9, 2010
 
Model for users
 
@author: marcink
 
"""
 

	
 
from pylons_app.model.db import User
 
from pylons_app.model.meta import Session
 
from pylons.i18n.translation import _
 
import logging
 
log = logging.getLogger(__name__)
 

	
 
class DefaultUserException(Exception):pass
 

	
 
class UserModel(object):
 

	
 
    def __init__(self):
 
        self.sa = Session() 
 
    
 
    def get_user(self, id):
 
        return self.sa.query(User).get(id)
 
    
 
    def create(self, form_data):
 
        try:
 
            new_user = User()
 
            for k, v in form_data.items():
 
                setattr(new_user, k, v)
 
                
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except Exception as e:
 
            log.error(e)
 
            self.sa.rollback()
 
            raise      
 
    
 
    def create_registration(self, form_data):
 
        try:
 
            new_user = User()
 
            for k, v in form_data.items():
 
                if k != 'admin' or k != 'active':
 
                    setattr(new_user, k, v)
 
                setattr(new_user, 'active', True)
 
                
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except Exception as e:
 
            log.error(e)
 
            self.sa.rollback()
 
            raise      
 
    
 
    def update(self, id, form_data):
 
        try:
 
            new_user = self.sa.query(User).get(id)
 
            if new_user.username == 'default':
 
                raise DefaultUserException(
 
                                _("You can't Edit this user since it's" 
 
                                  " crucial for entire application"))
 
            for k, v in form_data.items():
 
                if k == 'new_password' and v != '':
 
                    new_user.password = v
 
                else:
 
                    setattr(new_user, k, v)
 
                
 
            self.sa.add(new_user)
 
            self.sa.commit()
 
        except Exception as e:
 
            log.error(e)
 
            self.sa.rollback()
 
            raise      
 

	
 
    def delete(self, id):
 
        
 
        try:
 
            
 
            user = self.sa.query(User).get(id)
 
            if user.username == 'default':
 
                raise DefaultUserException(
 
                                _("You can't remove this user since it's" 
 
                                  " crucial for entire application"))
 
            self.sa.delete(user)
 
            self.sa.commit()            
 
        except Exception as e:
 
            log.error(e)
 
            self.sa.rollback()
 
            raise        
0 comments (0 inline, 0 general)