.. _changelog:
=========
Changelog
1.4.3 (**2012-XX-XX**)
----------------------
:status: in-progress
:branch: beta
news
++++
- #558 Added config file to hooks extra data
- bumbped mercurial version to 2.3.1
fixes
+++++
- fixed #570 explicit users group permissions can overwrite owner permissions
1.4.2 (**2012-09-12**)
- added option to menu to quick lock/unlock repository for users that have
write access to
- Implemented permissions for writing to repo
groups. Now only write access to group allows to create a repostiory
within that group
- #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
- updated translation for zh_CN
- fixed visual permissions check on repos groups inside groups
- fixed issues with non-ascii search terms in search, and indexers
- fixed parsing of page number in GET parameters
- fixed issues with generating pull-request overview for repos with
bookmarks and tags, also preview doesn't loose chosen revision from
select dropdown
1.4.1 (**2012-09-07**)
- always put a comment about code-review status change even if user send
empty data
- modified_on column saves repository update and it's going to be used
later for light version of main page ref #500
- pull request notifications send much nicer emails with details about pull
request
- #551 show breadcrumbs in summary view for repositories inside a group
- fixed migrations of permissions that can lead to inconsistency.
Some users sent feedback that after upgrading from older versions issues
with updating default permissions occurred. RhodeCode detects that now and
resets default user permission to initial state if there is a need for that.
Also forces users to set the default value for new forking permission.
- #535 improved apache wsgi example configuration in docs
- fixes #550 mercurial repositories comparision failed when origin repo had
additional not-common changesets
- fixed status of code-review in preview windows of pull request
- git forks were not initialized at bare repos
- fixes #555 fixes issues with comparing non-related repositories
- fixes #557 follower counter always counts up
- fixed issue #560 require push ssl checkbox wasn't shown when option was
enabled
- fixed #559
- fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
if it was a request to url by repository ID
1.4.0 (**2012-09-03**)
- new codereview system
- email map, allowing users to have multiple email addresses mapped into
their accounts
- improved git-hook system. Now all actions for git are logged into journal
including pushed revisions, user and IP address
- changed setup-app into setup-rhodecode and added default options to it.
- new git repos are created as bare now by default
- #464 added links to groups in permission box
- #465 mentions autocomplete inside comments boxes
- #469 added --update-only option to whoosh to re-index only given list
of repos in index
- rhodecode-api CLI client
- new git http protocol replaced buggy dulwich implementation.
Now based on pygrack & gitweb
- Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
reformated based on user suggestions. Additional rss/atom feeds for user
journal
- various i18n improvements
- #478 permissions overview for admin in user edit view
- File view now displays small gravatars off all authors of given file
- Implemented landing revisions. Each repository will get landing_rev attribute
that defines 'default' revision/branch for generating readme files
- Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
earliest possible call.
- Import remote svn repositories to mercurial using hgsubversion.
- Fixed #508 RhodeCode now has a option to explicitly set forking permissions
- RhodeCode can use alternative server for generating avatar icons
- implemented repositories locking. Pull locks, push unlocks. Also can be done
via API calls
- #538 form for permissions can handle multiple users at once
- improved translations
- fixes issue #455 Creating an archive generates an exception on Windows
- fixes #448 Download ZIP archive keeps file in /tmp open and results
in out of disk space
- fixes issue #454 Search results under Windows include proceeding
backslash
- fixed issue #450. Rhodecode no longer will crash when bad revision is
present in journal data.
- fix for issue #417, git execution was broken on windows for certain
commands.
- fixed #413. Don't disable .git directory for bare repos on deleting
- fixed issue #459. Changed the way of obtaining logger in reindex task.
- fixed #453 added ID field in whoosh SCHEMA that solves the issue of
reindexing modified files
- fixed #481 rhodecode emails are sent without Date header
- fixed #458 wrong count when no repos are present
- fixed issue #492 missing `\ No newline at end of file` test at the end of
new chunk in html diff
- full text search now works also for commit messages
1.3.6 (**2012-05-17**)
- chinese traditional translation
- changed setup-app into setup-rhodecode and added arguments for auto-setup
mode that doesn't need user interaction
- fixed no scm found warning
- fixed __future__ import error on rcextensions
- made simplejson required lib for speedup on JSON encoding
- fixes #449 bad regex could get more than revisions from parsing history
- don't clear DB session when CELERY_EAGER is turned ON
1.3.5 (**2012-05-10**)
- use ext_json for json module
- unified annotation view with file source view
- notification improvements, better inbox + css
- #419 don't strip passwords for login forms, make rhodecode
more compatible with LDAP servers
- Added HTTP_X_FORWARDED_FOR as another method of extracting
IP for pull/push logs. - moved all to base controller
- #415: Adding comment to changeset causes reload.
Comments are now added via ajax and doesn't reload the page
- #374 LDAP config is discarded when LDAP can't be activated
- limited push/pull operations are now logged for git in the journal
- bumped mercurial to 2.2.X series
- added support for displaying submodules in file-browser
- #421 added bookmarks in changelog view
- fixed dev-version marker for stable when served from source codes
- fixed missing permission checks on show forks page
- #418 cast to unicode fixes in notification objects
- #426 fixed mention extracting regex
- fixed remote-pulling for git remotes remopositories
- fixed #434: Error when accessing files or changesets of a git repository
with submodules
- fixed issue with empty APIKEYS for users after registration ref. #438
- fixed issue with getting README files from git repositories
1.3.4 (**2012-03-28**)
- Whoosh logging is now controlled by the .ini files logging setup
- added clone-url into edit form on /settings page
- added help text into repo add/edit forms
- created rcextensions module with additional mappings (ref #322) and
post push/pull/create repo hooks callbacks
- implemented #377 Users view for his own permissions on account page
- #399 added inheritance of permissions for users group on repos groups
- #401 repository group is automatically pre-selected when adding repos
inside a repository group
- added alternative HTTP 403 response when client failed to authenticate. Helps
solving issues with Mercurial and LDAP
- #402 removed group prefix from repository name when listing repositories
inside a group
- added gravatars into permission view and permissions autocomplete
@@ -335,335 +335,339 @@ class UserModel(BaseModel):
log.error(traceback.format_exc())
raise
def reset_password_link(self, data):
from rhodecode.lib.celerylib import tasks, run_task
run_task(tasks.send_password_link, data['email'])
def reset_password(self, data):
run_task(tasks.reset_user_password, data['email'])
def fill_data(self, auth_user, user_id=None, api_key=None):
"""
Fetches auth_user by user_id,or api_key if present.
Fills auth_user attributes with those taken from database.
Additionally set's is_authenitated if lookup fails
present in database
:param auth_user: instance of user to set attributes
:param user_id: user id to fetch by
:param api_key: api key to fetch by
if user_id is None and api_key is None:
raise Exception('You need to pass user_id or api_key')
try:
if api_key:
dbuser = self.get_by_api_key(api_key)
else:
dbuser = self.get(user_id)
if dbuser is not None and dbuser.active:
log.debug('filling %s data' % dbuser)
for k, v in dbuser.get_dict().items():
setattr(auth_user, k, v)
return False
except:
auth_user.is_authenticated = False
return True
def fill_perms(self, user):
Fills user permission attribute with permissions taken from database
works for permissions given for repositories, and for permissions that
are granted to groups
:param user: user instance to fill his perms
RK = 'repositories'
GK = 'repositories_groups'
GLOBAL = 'global'
user.permissions[RK] = {}
user.permissions[GK] = {}
user.permissions[GLOBAL] = set()
#======================================================================
# fetch default permissions
default_user = User.get_by_username('default', cache=True)
default_user_id = default_user.user_id
default_repo_perms = Permission.get_default_perms(default_user_id)
default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
if user.is_admin:
#==================================================================
# admin user have all default rights for repositories
# and groups set to admin
user.permissions[GLOBAL].add('hg.admin')
# repositories
for perm in default_repo_perms:
r_k = perm.UserRepoToPerm.repository.repo_name
p = 'repository.admin'
user.permissions[RK][r_k] = p
# repositories groups
for perm in default_repo_groups_perms:
rg_k = perm.UserRepoGroupToPerm.group.group_name
p = 'group.admin'
user.permissions[GK][rg_k] = p
return user
# SET DEFAULTS GLOBAL, REPOS, REPOS GROUPS
uid = user.user_id
# default global permissions taken fron the default user
default_global_perms = self.sa.query(UserToPerm)\
.filter(UserToPerm.user_id == default_user_id)
for perm in default_global_perms:
user.permissions[GLOBAL].add(perm.permission.permission_name)
# defaults for repositories, taken from default user
if perm.Repository.private and not (perm.Repository.user_id == uid):
# disable defaults for private repos,
p = 'repository.none'
elif perm.Repository.user_id == uid:
# set admin if owner
p = perm.Permission.permission_name
# defaults for repositories groups taken from default user permission
# on given group
# !! OVERRIDE GLOBALS !! with user permissions if any found
# those can be configured from groups or users explicitly
_configurable = set(['hg.fork.none', 'hg.fork.repository',
'hg.create.none', 'hg.create.repository'])
# USER GROUPS comes first
# users group global permissions
user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
.options(joinedload(UsersGroupToPerm.permission))\
.join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
UsersGroupMember.users_group_id))\
.filter(UsersGroupMember.user_id == uid)\
.order_by(UsersGroupToPerm.users_group_id)\
.all()
#need to group here by groups since user can be in more than one group
_grouped = [[x, list(y)] for x, y in
itertools.groupby(user_perms_from_users_groups,
lambda x:x.users_group)]
for gr, perms in _grouped:
# since user can be in multiple groups iterate over them and
# select the lowest permissions first (more explicit)
##TODO: do this^^
if not gr.inherit_default_permissions:
# NEED TO IGNORE all configurable permissions and
# replace them with explicitly set
user.permissions[GLOBAL] = user.permissions[GLOBAL]\
.difference(_configurable)
for perm in perms:
# user specific global permissions
user_perms = self.sa.query(UserToPerm)\
.options(joinedload(UserToPerm.permission))\
.filter(UserToPerm.user_id == uid).all()
if not user.inherit_default_permissions:
for perm in user_perms:
# !! REPO PERMISSIONS !!
# check if user is part of user groups for this repository and
# fill in (or NOT replace with higher `or 1` permissions
# users group for repositories permissions
user_repo_perms_from_users_groups = \
self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
.join((Repository, UsersGroupRepoToPerm.repository_id ==
Repository.repo_id))\
.join((Permission, UsersGroupRepoToPerm.permission_id ==
Permission.permission_id))\
.join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
for perm in user_repo_perms_from_users_groups:
r_k = perm.UsersGroupRepoToPerm.repository.repo_name
cur_perm = user.permissions[RK][r_k]
# overwrite permission only if it's greater than permission
# given from other sources
# given from other sources - disabled with `or 1` now
if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm] or 1: # disable check
if perm.Repository.user_id == uid:
# user explicit permissions for repositories
user_repo_perms = \
self.sa.query(UserRepoToPerm, Permission, Repository)\
.join((Repository, UserRepoToPerm.repository_id ==
.join((Permission, UserRepoToPerm.permission_id ==
.filter(UserRepoToPerm.user_id == uid)\
for perm in user_repo_perms:
# REPO GROUP
# get access for this user for repos group and override defaults
# user explicit permissions for repository
user_repo_groups_perms = \
self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
.join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
.join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
.filter(UserRepoGroupToPerm.user_id == uid)\
for perm in user_repo_groups_perms:
cur_perm = user.permissions[GK][rg_k]
# REPO GROUP + USER GROUP
# check if user is part of user groups for this repo group and
# fill in (or replace with higher) permissions
user_repo_group_perms_from_users_groups = \
self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
.join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
.join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
.join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
for perm in user_repo_group_perms_from_users_groups:
g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
cur_perm = user.permissions[GK][g_k]
user.permissions[GK][g_k] = p
def has_perm(self, user, perm):
perm = self._get_perm(perm)
user = self._get_user(user)
return UserToPerm.query().filter(UserToPerm.user == user)\
.filter(UserToPerm.permission == perm).scalar() is not None
def grant_perm(self, user, perm):
Grant user global permissions
:param user:
:param perm:
# if this permission is already granted skip it
_perm = UserToPerm.query()\
.filter(UserToPerm.user == user)\
.filter(UserToPerm.permission == perm)\
.scalar()
if _perm:
return
new = UserToPerm()
new.user = user
new.permission = perm
self.sa.add(new)
def revoke_perm(self, user, perm):
Revoke users global permissions
obj = UserToPerm.query()\
if obj:
self.sa.delete(obj)
def add_extra_email(self, user, email):
Adds email address to UserEmailMap
:param email:
from rhodecode.model import forms
form = forms.UserExtraEmailForm()()
data = form.to_python(dict(email=email))
obj = UserEmailMap()
obj.user = user
obj.email = data['email']
self.sa.add(obj)
return obj
def delete_extra_email(self, user, email_id):
Removes email address from UserEmailMap
:param email_id:
obj = UserEmailMap.query().get(email_id)
import os
import unittest
from rhodecode.tests import *
from rhodecode.tests.models.common import _make_group
from rhodecode.model.repos_group import ReposGroupModel
from rhodecode.model.repo import RepoModel
from rhodecode.model.db import RepoGroup, User, UsersGroupRepoGroupToPerm
from rhodecode.model.user import UserModel
from rhodecode.model.meta import Session
from rhodecode.model.users_group import UsersGroupModel
from rhodecode.lib.auth import AuthUser
from rhodecode.tests.api.api_base import create_repo
class TestPermissions(unittest.TestCase):
def __init__(self, methodName='runTest'):
super(TestPermissions, self).__init__(methodName=methodName)
def setUp(self):
self.u1 = UserModel().create_or_update(
username=u'u1', password=u'qweqwe',
email=u'u1@rhodecode.org', firstname=u'u1', lastname=u'u1'
)
self.u2 = UserModel().create_or_update(
username=u'u2', password=u'qweqwe',
email=u'u2@rhodecode.org', firstname=u'u2', lastname=u'u2'
self.u3 = UserModel().create_or_update(
username=u'u3', password=u'qweqwe',
email=u'u3@rhodecode.org', firstname=u'u3', lastname=u'u3'
self.anon = User.get_by_username('default')
self.a1 = UserModel().create_or_update(
username=u'a1', password=u'qweqwe',
email=u'a1@rhodecode.org', firstname=u'a1', lastname=u'a1', admin=True
Session().commit()
def tearDown(self):
if hasattr(self, 'test_repo'):
RepoModel().delete(repo=self.test_repo)
UserModel().delete(self.u1)
UserModel().delete(self.u2)
UserModel().delete(self.u3)
UserModel().delete(self.a1)
if hasattr(self, 'g1'):
ReposGroupModel().delete(self.g1.group_id)
if hasattr(self, 'g2'):
ReposGroupModel().delete(self.g2.group_id)
if hasattr(self, 'ug1'):
UsersGroupModel().delete(self.ug1, force=True)
def test_default_perms_set(self):
u1_auth = AuthUser(user_id=self.u1.user_id)
perms = {
'repositories_groups': {},
'global': set([u'hg.create.repository', u'repository.read',
u'hg.register.manual_activate']),
'repositories': {u'vcs_test_hg': u'repository.read'}
}
self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
perms['repositories'][HG_REPO])
new_perm = 'repository.write'
RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
perm=new_perm)
new_perm)
def test_default_admin_perms_set(self):
a1_auth = AuthUser(user_id=self.a1.user_id)
'global': set([u'hg.admin']),
'repositories': {u'vcs_test_hg': u'repository.admin'}
self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1,
# cannot really downgrade admins permissions !? they still get's set as
# admin !
u1_auth = AuthUser(user_id=self.a1.user_id)
def test_default_group_perms(self):
self.g1 = _make_group('test1', skip_if_exists=True)
self.g2 = _make_group('test2', skip_if_exists=True)
'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']),
self.assertEqual(u1_auth.permissions['repositories_groups'],
perms['repositories_groups'])
def test_default_admin_group_perms(self):
'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
'global': set(['hg.admin']),
'repositories': {u'vcs_test_hg': 'repository.admin'}
self.assertEqual(a1_auth.permissions['repositories_groups'],
def test_propagated_permission_from_users_group_by_explicit_perms_exist(self):
# make group
self.ug1 = UsersGroupModel().create('G1')
# add user to group
UsersGroupModel().add_user_to_group(self.ug1, self.u1)
# set permission to lower
new_perm = 'repository.none'
RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
# grant perm for group this should not override permission from user
# since it has explicitly set
new_perm_gr = 'repository.write'
RepoModel().grant_users_group_permission(repo=HG_REPO,
group_name=self.ug1,
perm=new_perm_gr)
# check perms
def test_propagated_permission_from_users_group(self):
UsersGroupModel().add_user_to_group(self.ug1, self.u3)
# grant perm for group this should override default permission from user
u3_auth = AuthUser(user_id=self.u3.user_id)
self.assertEqual(u3_auth.permissions['repositories'][HG_REPO],
new_perm_gr)
self.assertEqual(u3_auth.permissions['repositories_groups'],
def test_propagated_permission_from_users_group_lower_weight(self):
new_perm_h = 'repository.write'
perm=new_perm_h)
new_perm_h)
# grant perm for group this should NOT override permission from user
# since it's lower than granted
new_perm_l = 'repository.read'
perm=new_perm_l)
'repositories': {u'vcs_test_hg': u'repository.write'}
def test_repo_in_group_permissions(self):
self.g1 = _make_group('group1', skip_if_exists=True)
self.g2 = _make_group('group2', skip_if_exists=True)
# both perms should be read !
{u'group1': u'group.read', u'group2': u'group.read'})
a1_auth = AuthUser(user_id=self.anon.user_id)
#Change perms to none for both groups
ReposGroupModel().grant_user_permission(repos_group=self.g1,
user=self.anon,
perm='group.none')
ReposGroupModel().grant_user_permission(repos_group=self.g2,
@@ -236,192 +237,236 @@ class TestPermissions(unittest.TestCase)
{u'group1': u'group.none', u'group2': u'group.none'})
# add repo to group
name = RepoGroup.url_sep().join([self.g1.group_name, 'test_perm'])
self.test_repo = RepoModel().create_repo(
repo_name=name,
repo_type='hg',
description='',
repos_group=self.g1,
owner=self.u1,
#grant permission for u2 !
user=self.u2,
perm='group.read')
self.assertNotEqual(self.u1, self.u2)
#u1 and anon should have not change perms while u2 should !
u2_auth = AuthUser(user_id=self.u2.user_id)
self.assertEqual(u2_auth.permissions['repositories_groups'],
def test_repo_group_user_as_user_group_member(self):
# create Group1
{u'group1': u'group.read'})
# set default permission to none
# check if user is in the group
membrs = [x.user_id for x in UsersGroupModel().get(self.ug1.users_group_id).members]
self.assertEqual(membrs, [self.u1.user_id])
# add some user to that group
# check his permissions
{u'group1': u'group.none'})
# grant ug1 read permissions for
ReposGroupModel().grant_users_group_permission(repos_group=self.g1,
# check if the
obj = Session().query(UsersGroupRepoGroupToPerm)\
.filter(UsersGroupRepoGroupToPerm.group == self.g1)\
.filter(UsersGroupRepoGroupToPerm.users_group == self.ug1)\
self.assertEqual(obj.permission.permission_name, 'group.read')
def test_inherited_permissions_from_default_on_user_enabled(self):
user_model = UserModel()
# enable fork and create on default user
usr = 'default'
user_model.revoke_perm(usr, 'hg.create.none')
user_model.grant_perm(usr, 'hg.create.repository')
user_model.revoke_perm(usr, 'hg.fork.none')
user_model.grant_perm(usr, 'hg.fork.repository')
# make sure inherit flag is turned on
self.u1.inherit_default_permissions = True
# this user will have inherited permissions from default user
self.assertEqual(u1_auth.permissions['global'],
set(['hg.create.repository', 'hg.fork.repository',
'hg.register.manual_activate',
'repository.read']))
def test_inherited_permissions_from_default_on_user_disabled(self):
# disable fork and create on default user
user_model.revoke_perm(usr, 'hg.create.repository')
user_model.grant_perm(usr, 'hg.create.none')
user_model.revoke_perm(usr, 'hg.fork.repository')
user_model.grant_perm(usr, 'hg.fork.none')
set(['hg.create.none', 'hg.fork.none',
def test_non_inherited_permissions_from_default_on_user_enabled(self):
#disable global perms on specific user
user_model.revoke_perm(self.u1, 'hg.create.repository')
user_model.grant_perm(self.u1, 'hg.create.none')
user_model.revoke_perm(self.u1, 'hg.fork.repository')
user_model.grant_perm(self.u1, 'hg.fork.none')
# make sure inherit flag is turned off
self.u1.inherit_default_permissions = False
# this user will have non inherited permissions from he's
# explicitly set permissions
def test_non_inherited_permissions_from_default_on_user_disabled(self):
#enable global perms on specific user
user_model.revoke_perm(self.u1, 'hg.create.none')
user_model.grant_perm(self.u1, 'hg.create.repository')
user_model.revoke_perm(self.u1, 'hg.fork.none')
user_model.grant_perm(self.u1, 'hg.fork.repository')
def test_owner_permissions_doesnot_get_overwritten_by_group(self):
#create repo as USER,
self.test_repo = repo = RepoModel().create_repo(repo_name='myownrepo',
description='desc',
owner=self.u1)
#he has permissions of admin as owner
self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
'repository.admin')
#set his permission as users group, he should still be admin
RepoModel().grant_users_group_permission(repo, group_name=self.ug1,
perm='repository.none')
def test_owner_permissions_doesnot_get_overwritten_by_others(self):
#set his permission as user, he should still be admin
RepoModel().grant_user_permission(repo, user=self.u1,
Status change: