@@ -96,43 +96,70 @@ def make_map(config):
"/repos_public_journal/{repo_name:.*}",
action="repo_public_journal", conditions=dict(method=["PUT"],
function=check_repo))
m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
action="repo_pull", conditions=dict(method=["PUT"],
#ADMIN REPOS GROUP REST ROUTES
rmap.resource('repos_group', 'repos_groups',
controller='admin/repos_groups', path_prefix='/_admin')
#ADMIN USER REST ROUTES
rmap.resource('user', 'users', controller='admin/users',
path_prefix='/_admin')
with rmap.submapper(path_prefix='/_admin', controller='admin/users') as m:
m.connect("users", "/users",
action="create", conditions=dict(method=["POST"]))
action="index", conditions=dict(method=["GET"]))
m.connect("formatted_users", "/users.{format}",
m.connect("new_user", "/users/new",
action="new", conditions=dict(method=["GET"]))
m.connect("formatted_new_user", "/users/new.{format}",
m.connect("update_user", "/users/{id}",
action="update", conditions=dict(method=["PUT"]))
m.connect("delete_user", "/users/{id}",
action="delete", conditions=dict(method=["DELETE"]))
m.connect("edit_user", "/users/{id}/edit",
action="edit", conditions=dict(method=["GET"]))
m.connect("formatted_edit_user",
"/users/{id}.{format}/edit",
m.connect("user", "/users/{id}",
action="show", conditions=dict(method=["GET"]))
m.connect("formatted_user", "/users/{id}.{format}",
#EXTRAS USER ROUTES
m.connect("user_perm", "/users_perm/{id}",
action="update_perm", conditions=dict(method=["PUT"]))
#ADMIN USERS REST ROUTES
rmap.resource('users_group', 'users_groups',
controller='admin/users_groups', path_prefix='/_admin')
#ADMIN GROUP REST ROUTES
rmap.resource('group', 'groups', controller='admin/groups',
rmap.resource('group', 'groups',
controller='admin/groups', path_prefix='/_admin')
#ADMIN PERMISSIONS REST ROUTES
rmap.resource('permission', 'permissions',
controller='admin/permissions', path_prefix='/_admin')
##ADMIN LDAP SETTINGS
rmap.connect('ldap_settings', '/_admin/ldap',
controller='admin/ldap_settings', action='ldap_settings',
conditions=dict(method=["POST"]))
rmap.connect('ldap_home', '/_admin/ldap',
controller='admin/ldap_settings')
#ADMIN SETTINGS REST ROUTES
with rmap.submapper(path_prefix='/_admin',
controller='admin/settings') as m:
m.connect("admin_settings", "/settings",
m.connect("formatted_admin_settings", "/settings.{format}",
@@ -24,25 +24,24 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from formencode import htmlfill
from pylons import request, session, tmpl_context as c, url
from pylons.controllers.util import abort, redirect
from pylons.i18n.translation import _
from rhodecode.lib import helpers as h
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
from rhodecode.lib.auth_ldap import LdapImportError
from rhodecode.lib.base import BaseController, render
from rhodecode.model.forms import LdapSettingsForm, DefaultPermissionsForm
from rhodecode.model.permission import PermissionModel
from rhodecode.model.settings import SettingsModel
from rhodecode.model.user import UserModel
import formencode
import logging
import traceback
log = logging.getLogger(__name__)
class PermissionsController(BaseController):
"""REST Controller styled on the Atom Publishing Protocol"""
# To properly map this controller, ensure your config/routing.py
# file has a resource setup:
@@ -29,25 +29,25 @@ import formencode
from pylons import request, session, tmpl_context as c, url, config
from rhodecode.lib.exceptions import DefaultUserException, \
UserOwnsReposException
from rhodecode.model.db import User
from rhodecode.model.db import User, RepoToPerm, UserToPerm, Permission
from rhodecode.model.forms import UserForm
class UsersController(BaseController):
# map.resource('user', 'users')
@@ -92,58 +92,61 @@ class UsersController(BaseController):
return redirect(url('users'))
def new(self, format='html'):
"""GET /users/new: Form to create a new item"""
# url('new_user')
return render('admin/users/user_add.html')
def update(self, id):
"""PUT /users/id: Update an existing item"""
# Forms posted to this method should contain a hidden field:
# <input type="hidden" name="_method" value="PUT" />
# Or using helpers:
# h.form(url('user', id=ID),
# h.form(url('update_user', id=ID),
# method='put')
# url('user', id=ID)
user_model = UserModel()
c.user = user_model.get(id)
_form = UserForm(edit=True, old_data={'user_id': id,
'email': c.user.email})()
form_result = {}
try:
form_result = _form.to_python(dict(request.POST))
user_model.update(id, form_result)
h.flash(_('User updated succesfully'), category='success')
h.flash(_('User updated successfully'), category='success')
except formencode.Invalid, errors:
e = errors.error_dict or {}
perm = Permission.get_by_key('hg.create.repository')
e.update({'create_repo_perm': UserToPerm.has_perm(id, perm)})
return htmlfill.render(
render('admin/users/user_edit.html'),
defaults=errors.value,
errors=errors.error_dict or {},
errors=e,
prefix_error=False,
encoding="UTF-8")
except Exception:
log.error(traceback.format_exc())
h.flash(_('error occurred during update of user %s') \
% form_result.get('username'), category='error')
def delete(self, id):
"""DELETE /users/id: Delete an existing item"""
# <input type="hidden" name="_method" value="DELETE" />
# h.form(url('delete_user', id=ID),
# method='delete')
user_model.delete(id)
h.flash(_('successfully deleted user'), category='success')
except (UserOwnsReposException, DefaultUserException), e:
h.flash(str(e), category='warning')
h.flash(_('An error occurred during deletion of user'),
category='error')
@@ -158,19 +161,47 @@ class UsersController(BaseController):
if not c.user:
if c.user.username == 'default':
h.flash(_("You can't edit this user"), category='warning')
c.user.permissions = {}
c.granted_permissions = user_model.fill_perms(c.user)\
.permissions['global']
defaults = c.user.get_dict()
defaults.update({'create_repo_perm': UserToPerm.has_perm(id, perm)})
defaults=defaults,
encoding="UTF-8",
force_defaults=False
)
def update_perm(self, id):
"""PUT /users_perm/id: Update an existing item"""
# url('user_perm', id=ID, method='put')
grant_perm = request.POST.get('create_repo_perm', False)
if grant_perm:
perm = Permission.get_by_key('hg.create.none')
UserToPerm.revoke_perm(id, perm)
UserToPerm.grant_perm(id, perm)
h.flash(_("Granted 'repository create' permission to user"),
category='success')
else:
h.flash(_("Revoked 'repository create' permission to user"),
return redirect(url('edit_user', id=id))
@@ -27,31 +27,32 @@
import os
import sys
import uuid
from os.path import dirname as dn, join as jn
from rhodecode import __dbversion__
from rhodecode.model import meta
from rhodecode.lib.auth import get_crypt_password, generate_api_key
from rhodecode.lib.utils import ask_ok
from rhodecode.model import init_model
from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \
UserToPerm, DbMigrateVersion
from rhodecode.model.db import User, Permission, RhodeCodeUi, \
RhodeCodeSettings, UserToPerm, DbMigrateVersion
from sqlalchemy.engine import create_engine
class DbManage(object):
def __init__(self, log_sql, dbconf, root, tests=False):
self.dbname = dbconf.split('/')[-1]
self.tests = tests
self.root = root
self.dburi = dbconf
self.log_sql = log_sql
self.db_exists = False
self.init_db()
def init_db(self):
engine = create_engine(self.dburi, echo=self.log_sql)
@@ -67,40 +68,37 @@ class DbManage(object):
destroy = True
destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
if not destroy:
sys.exit()
if destroy:
meta.Base.metadata.drop_all()
checkfirst = not override
meta.Base.metadata.create_all(checkfirst=checkfirst)
log.info('Created tables for %s', self.dbname)
def set_db_version(self):
ver = DbMigrateVersion()
ver.version = __dbversion__
ver.repository_id = 'rhodecode_db_migrations'
ver.repository_path = 'versions'
self.sa.add(ver)
self.sa.commit()
except:
self.sa.rollback()
raise
log.info('db version set to: %s', __dbversion__)
def upgrade(self):
"""Upgrades given database schema to given revision following
all needed steps, to perform the upgrade
"""
from rhodecode.lib.dbmigrate.migrate.versioning import api
from rhodecode.lib.dbmigrate.migrate.exceptions import \
DatabaseNotControlledError
upgrade = ask_ok('You are about to perform database upgrade, make '
'sure You backed up your database before. '
@@ -161,26 +159,24 @@ class DbManage(object):
def step_3(self):
print ('Adding additional settings into RhodeCode db')
self.klass.fix_settings()
upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
#CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
for step in upgrade_steps:
print ('performing upgrade step %s' % step)
callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
def fix_repo_paths(self):
"""Fixes a old rhodecode version path into new one without a '*'
paths = self.sa.query(RhodeCodeUi)\
.filter(RhodeCodeUi.ui_key == '/')\
.scalar()
paths.ui_value = paths.ui_value.replace('*', '')
self.sa.add(paths)
@@ -216,54 +212,57 @@ class DbManage(object):
self.sa.add(hgsettings3)
def admin_prompt(self, second=False):
if not self.tests:
import getpass
def get_password():
password = getpass.getpass('Specify admin password (min 6 chars):')
password = getpass.getpass('Specify admin password '
'(min 6 chars):')
confirm = getpass.getpass('Confirm password:')
if password != confirm:
log.error('passwords mismatch')
return False
if len(password) < 6:
log.error('password is to short use at least 6 characters')
return password
username = raw_input('Specify admin username:')
password = get_password()
if not password:
#second try
email = raw_input('Specify admin email:')
self.create_user(username, password, email, True)
log.info('creating admin and regular test users')
self.create_user('test_admin', 'test12', 'test_admin@mail.com', True)
self.create_user('test_regular', 'test12', 'test_regular@mail.com', False)
self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False)
self.create_user('test_admin', 'test12',
'test_admin@mail.com', True)
self.create_user('test_regular', 'test12',
'test_regular@mail.com', False)
self.create_user('test_regular2', 'test12',
'test_regular2@mail.com', False)
def create_ui_settings(self):
"""Creates ui settings, fills out hooks
and disables dotencode
#HOOKS
hooks1_key = 'changegroup.update'
hooks1_ = self.sa.query(RhodeCodeUi)\
.filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
@@ -299,38 +298,36 @@ class DbManage(object):
self.sa.add(hooks1)
self.sa.add(hooks2)
self.sa.add(hooks3)
self.sa.add(hooks4)
self.sa.add(dotencode_disable)
def create_ldap_options(self):
"""Creates ldap settings"""
for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
('ldap_port', '389'), ('ldap_ldaps', 'false'),
('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
('ldap_dn_pass', ''), ('ldap_base_dn', ''),
('ldap_filter', ''), ('ldap_search_scope', ''),
('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
setting = RhodeCodeSettings(k, v)
self.sa.add(setting)
def config_prompt(self, test_repo_path='', retries=3):
if retries == 3:
log.info('Setting up repositories config')
if not self.tests and not test_repo_path:
@@ -344,32 +341,30 @@ class DbManage(object):
if not os.path.isdir(path):
path_ok = False
log.error('Entered path is not a valid directory: %s [%s/3]',
path, retries)
#check write access
if not os.access(path, os.W_OK):
log.error('No write permission to given path: %s [%s/3]',
if retries == 0:
if path_ok is False:
retries -= 1
return self.config_prompt(test_repo_path, retries)
return path
def create_settings(self, path):
self.create_ui_settings()
#HG UI OPTIONS
web1 = RhodeCodeUi()
web1.ui_section = 'web'
web1.ui_key = 'push_ssl'
web1.ui_value = 'false'
@@ -384,30 +379,28 @@ class DbManage(object):
web3.ui_value = '*'
web4 = RhodeCodeUi()
web4.ui_section = 'web'
web4.ui_key = 'baseurl'
web4.ui_value = '/'
paths = RhodeCodeUi()
paths.ui_section = 'paths'
paths.ui_key = '/'
paths.ui_value = path
hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
hgsettings3 = RhodeCodeSettings('ga_code', '')
self.sa.add(web1)
self.sa.add(web2)
self.sa.add(web3)
self.sa.add(web4)
self.sa.add(hgsettings1)
self.sa.add(hgsettings2)
@@ -458,26 +451,31 @@ class DbManage(object):
def create_permissions(self):
#module.(access|create|change|delete)_[name]
#module.(read|write|owner)
perms = [('repository.none', 'Repository no access'),
('repository.read', 'Repository read access'),
('repository.write', 'Repository write access'),
('repository.admin', 'Repository admin access'),
('hg.admin', 'Hg Administrator'),
('hg.create.repository', 'Repository create'),
('hg.create.none', 'Repository creation disabled'),
('hg.register.none', 'Register disabled'),
('hg.register.manual_activate', 'Register new user with RhodeCode without manual activation'),
('hg.register.auto_activate', 'Register new user with RhodeCode without auto activation'),
('hg.register.manual_activate', 'Register new user with '
'RhodeCode without manual'
'activation'),
('hg.register.auto_activate', 'Register new user with '
'RhodeCode without auto '
]
for p in perms:
new_perm = Permission()
new_perm.permission_name = p[0]
new_perm.permission_longname = p[1]
self.sa.add(new_perm)
@@ -24,25 +24,27 @@
import datetime
from datetime import date
from sqlalchemy import *
from sqlalchemy.exc import DatabaseError
from sqlalchemy.orm import relationship, backref
from sqlalchemy.orm.interfaces import MapperExtension
from rhodecode.lib import str2bool
from rhodecode.model.meta import Base, Session
from rhodecode.model.caching_query import FromCache
#==============================================================================
# MAPPER EXTENSIONS
class RepositoryMapper(MapperExtension):
def after_update(self, mapper, connection, instance):
pass
@@ -52,24 +54,53 @@ class RhodeCodeSettings(Base):
app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
def __init__(self, k='', v=''):
self.app_settings_name = k
self.app_settings_value = v
def __repr__(self):
return "<%s('%s:%s')>" % (self.__class__.__name__,
self.app_settings_name, self.app_settings_value)
@classmethod
def get_app_settings(cls, cache=False):
ret = Session.query(cls)
if cache:
ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
if not ret:
raise Exception('Could not get application settings !')
settings = {}
for each in ret:
settings['rhodecode_' + each.app_settings_name] = \
each.app_settings_value
return settings
def get_ldap_settings(cls, cache=False):
ret = Session.query(cls)\
.filter(cls.app_settings_name.startswith('ldap_'))\
.all()
fd = {}
for row in ret:
fd.update({row.app_settings_name:str2bool(row.app_settings_value)})
return fd
class RhodeCodeUi(Base):
__tablename__ = 'rhodecode_ui'
__table_args__ = {'useexisting':True}
ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
class User(Base):
__tablename__ = 'users'
@@ -276,46 +307,84 @@ class Group(Base):
class Permission(Base):
__tablename__ = 'permissions'
permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
self.permission_id, self.permission_name)
def get_by_key(cls, key):
return Session.query(cls).filter(cls.permission_name == key).scalar()
class RepoToPerm(Base):
__tablename__ = 'repo_to_perm'
__table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
user = relationship('User')
permission = relationship('Permission')
repository = relationship('Repository')
class UserToPerm(Base):
__tablename__ = 'user_to_perm'
__table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
def has_perm(cls, user_id, perm):
if not isinstance(perm, Permission):
raise Exception('perm needs to be an instance of Permission class')
return Session.query(cls).filter(cls.user_id == user_id)\
.filter(cls.permission == perm).scalar() is not None
def grant_perm(cls, user_id, perm):
new = cls()
new.user_id = user_id
new.permission = perm
Session.add(new)
Session.commit()
Session.rollback()
def revoke_perm(cls, user_id, perm):
Session.query(cls).filter(cls.user_id == user_id)\
.filter(cls.permission == perm).delete()
class UsersGroupToPerm(Base):
__tablename__ = 'users_group_to_perm'
__table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True})
users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
users_group = relationship('UsersGroup')
@@ -15,25 +15,25 @@
<%def name="page_nav()">
${self.menu('admin')}
</%def>
<%def name="main()">
<div class="box box-left">
<!-- box / title -->
<div class="title">
${self.breadcrumbs()}
</div>
<!-- end box / title -->
${h.form(url('user', id=c.user.user_id),method='put')}
${h.form(url('update_user', id=c.user.user_id),method='put')}
<div class="form">
<div class="field">
<div class="gravatar_box">
<div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
<p>
<strong>Change your avatar at <a href="http://gravatar.com">gravatar.com</a></strong><br/>
${_('Using')} ${c.user.email}
</p>
<div class="label">
@@ -117,33 +117,33 @@
${h.submit('save','Save',class_="ui-button")}
${h.reset('reset','Reset',class_="ui-button")}
${h.end_form()}
<div class="box box-right">
<h5>${_('Permissions')}</h5>
${h.form(url('user_perm', id=c.user.user_id),method='put')}
<!-- fields -->
<div class="fields">
<div class="label label-checkbox">
<label for="">${_('Create repositories')}:</label>
<div class="checkboxes">
${h.checkbox('create',value=True)}
${h.checkbox('create_repo_perm',value=True)}
<div class="buttons">
@@ -42,24 +42,24 @@
%for cnt,user in enumerate(c.users_list):
%if user.name !='default':
<tr class="parity${cnt%2}">
<td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div></td>
<td>${h.link_to(user.username,h.url('edit_user', id=user.user_id))}</td>
<td>${user.name}</td>
<td>${user.lastname}</td>
<td>${user.last_login}</td>
<td>${h.bool2icon(user.active)}</td>
<td>${h.bool2icon(user.admin)}</td>
<td>${h.bool2icon(bool(user.ldap_dn))}</td>
<td>
${h.form(url('user', id=user.user_id),method='delete')}
${h.form(url('delete_user', id=user.user_id),method='delete')}
${h.submit('remove_','delete',id="remove_user_%s" % user.user_id,
class_="delete_icon action_button",onclick="return confirm('Confirm to delete this user');")}
</td>
</tr>
%endif
%endfor
</table>
@@ -238,25 +238,25 @@ ${h.end_form()}
var choosen = D.get(selected_container);
for (var i = 0; i < choosen.options.length; i++) {
choosen.options[i].selected = 'selected';
}
})
});
</script>
${h.form(url('user', id=''),method='put')}
${h.form(url('xxx', id=''),method='put')}
Status change: