diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py
--- a/rhodecode/apps/_base/__init__.py
+++ b/rhodecode/apps/_base/__init__.py
@@ -30,6 +30,7 @@ from rhodecode.lib.vcs.exceptions import
from rhodecode.model import repo
from rhodecode.model import repo_group
from rhodecode.model import user_group
+from rhodecode.model import user
from rhodecode.model.db import User
from rhodecode.model.scm import ScmModel
@@ -267,6 +268,20 @@ class UserGroupAppView(BaseAppView):
self.db_user_group_name = self.db_user_group.users_group_name
+class UserAppView(BaseAppView):
+ def __init__(self, context, request):
+ super(UserAppView, self).__init__(context, request)
+ self.db_user = request.db_user
+ self.db_user_id = self.db_user.user_id
+
+ _ = self.request.translate
+ if not request.db_user_supports_default:
+ if self.db_user.username == User.DEFAULT_USER:
+ h.flash(_("Editing user `{}` is disabled.".format(
+ User.DEFAULT_USER)), category='warning')
+ raise HTTPFound(h.route_path('users'))
+
+
class DataGridAppView(object):
"""
Common class to have re-usable grid rendering components
@@ -483,17 +498,63 @@ class UserGroupRoutePredicate(object):
user_group_id = info['match']['user_group_id']
user_group_model = user_group.UserGroup()
- by_name_match = user_group_model.get(
+ by_id_match = user_group_model.get(
user_group_id, cache=True)
- if by_name_match:
+ if by_id_match:
# register this as request object we can re-use later
- request.db_user_group = by_name_match
+ request.db_user_group = by_id_match
return True
return False
+class UserRoutePredicateBase(object):
+ supports_default = None
+
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ raise NotImplementedError()
+
+ def __call__(self, info, request):
+ if hasattr(request, 'vcs_call'):
+ # skip vcs calls
+ return
+
+ user_id = info['match']['user_id']
+ user_model = user.User()
+ by_id_match = user_model.get(
+ user_id, cache=True)
+
+ if by_id_match:
+ # register this as request object we can re-use later
+ request.db_user = by_id_match
+ request.db_user_supports_default = self.supports_default
+ return True
+
+ return False
+
+
+class UserRoutePredicate(UserRoutePredicateBase):
+ supports_default = False
+
+ def text(self):
+ return 'user_route = %s' % self.val
+
+ phash = text
+
+
+class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
+ supports_default = True
+
+ def text(self):
+ return 'user_with_default_route = %s' % self.val
+
+ phash = text
+
+
def includeme(config):
config.add_route_predicate(
'repo_route', RepoRoutePredicate)
@@ -503,3 +564,7 @@ def includeme(config):
'repo_group_route', RepoGroupRoutePredicate)
config.add_route_predicate(
'user_group_route', UserGroupRoutePredicate)
+ config.add_route_predicate(
+ 'user_route_with_default', UserRouteWithDefaultPredicate)
+ config.add_route_predicate(
+ 'user_route', UserRoutePredicate)
\ No newline at end of file
diff --git a/rhodecode/apps/admin/__init__.py b/rhodecode/apps/admin/__init__.py
--- a/rhodecode/apps/admin/__init__.py
+++ b/rhodecode/apps/admin/__init__.py
@@ -137,74 +137,133 @@ def admin_routes(config):
name='users_data',
pattern='/users_data')
+ config.add_route(
+ name='users_create',
+ pattern='/users/create')
+
+ config.add_route(
+ name='users_new',
+ pattern='/users/new')
+
+ # user management
+ config.add_route(
+ name='user_edit',
+ pattern='/users/{user_id:\d+}/edit',
+ user_route=True)
+ config.add_route(
+ name='user_edit_advanced',
+ pattern='/users/{user_id:\d+}/edit/advanced',
+ user_route=True)
+ config.add_route(
+ name='user_edit_global_perms',
+ pattern='/users/{user_id:\d+}/edit/global_permissions',
+ user_route=True)
+ config.add_route(
+ name='user_edit_global_perms_update',
+ pattern='/users/{user_id:\d+}/edit/global_permissions/update',
+ user_route=True)
+ config.add_route(
+ name='user_update',
+ pattern='/users/{user_id:\d+}/update',
+ user_route=True)
+ config.add_route(
+ name='user_delete',
+ pattern='/users/{user_id:\d+}/delete',
+ user_route=True)
+ config.add_route(
+ name='user_force_password_reset',
+ pattern='/users/{user_id:\d+}/password_reset',
+ user_route=True)
+ config.add_route(
+ name='user_create_personal_repo_group',
+ pattern='/users/{user_id:\d+}/create_repo_group',
+ user_route=True)
+
# user auth tokens
config.add_route(
name='edit_user_auth_tokens',
- pattern='/users/{user_id:\d+}/edit/auth_tokens')
+ pattern='/users/{user_id:\d+}/edit/auth_tokens',
+ user_route=True)
config.add_route(
name='edit_user_auth_tokens_add',
- pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
+ pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
+ user_route=True)
config.add_route(
name='edit_user_auth_tokens_delete',
- pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
+ pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
+ user_route=True)
# user ssh keys
config.add_route(
name='edit_user_ssh_keys',
- pattern='/users/{user_id:\d+}/edit/ssh_keys')
+ pattern='/users/{user_id:\d+}/edit/ssh_keys',
+ user_route=True)
config.add_route(
name='edit_user_ssh_keys_generate_keypair',
- pattern='/users/{user_id:\d+}/edit/ssh_keys/generate')
+ pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
+ user_route=True)
config.add_route(
name='edit_user_ssh_keys_add',
- pattern='/users/{user_id:\d+}/edit/ssh_keys/new')
+ pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
+ user_route=True)
config.add_route(
name='edit_user_ssh_keys_delete',
- pattern='/users/{user_id:\d+}/edit/ssh_keys/delete')
+ pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
+ user_route=True)
# user emails
config.add_route(
name='edit_user_emails',
- pattern='/users/{user_id:\d+}/edit/emails')
+ pattern='/users/{user_id:\d+}/edit/emails',
+ user_route=True)
config.add_route(
name='edit_user_emails_add',
- pattern='/users/{user_id:\d+}/edit/emails/new')
+ pattern='/users/{user_id:\d+}/edit/emails/new',
+ user_route=True)
config.add_route(
name='edit_user_emails_delete',
- pattern='/users/{user_id:\d+}/edit/emails/delete')
+ pattern='/users/{user_id:\d+}/edit/emails/delete',
+ user_route=True)
# user IPs
config.add_route(
name='edit_user_ips',
- pattern='/users/{user_id:\d+}/edit/ips')
+ pattern='/users/{user_id:\d+}/edit/ips',
+ user_route=True)
config.add_route(
name='edit_user_ips_add',
- pattern='/users/{user_id:\d+}/edit/ips/new')
+ pattern='/users/{user_id:\d+}/edit/ips/new',
+ user_route_with_default=True) # enabled for default user too
config.add_route(
name='edit_user_ips_delete',
- pattern='/users/{user_id:\d+}/edit/ips/delete')
+ pattern='/users/{user_id:\d+}/edit/ips/delete',
+ user_route_with_default=True) # enabled for default user too
# user perms
config.add_route(
name='edit_user_perms_summary',
- pattern='/users/{user_id:\d+}/edit/permissions_summary')
+ pattern='/users/{user_id:\d+}/edit/permissions_summary',
+ user_route=True)
config.add_route(
name='edit_user_perms_summary_json',
- pattern='/users/{user_id:\d+}/edit/permissions_summary/json')
+ pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
+ user_route=True)
# user user groups management
config.add_route(
name='edit_user_groups_management',
- pattern='/users/{user_id:\d+}/edit/groups_management')
+ pattern='/users/{user_id:\d+}/edit/groups_management',
+ user_route=True)
config.add_route(
name='edit_user_groups_management_updates',
- pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
+ pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
+ user_route=True)
# user audit logs
config.add_route(
name='edit_user_audit_logs',
- pattern='/users/{user_id:\d+}/edit/audit')
+ pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
# user-groups admin
config.add_route(
diff --git a/rhodecode/apps/admin/tests/test_admin_users.py b/rhodecode/apps/admin/tests/test_admin_users.py
--- a/rhodecode/apps/admin/tests/test_admin_users.py
+++ b/rhodecode/apps/admin/tests/test_admin_users.py
@@ -19,8 +19,12 @@
# and proprietary license terms, please see https://rhodecode.com/licenses/
import pytest
+from sqlalchemy.orm.exc import NoResultFound
-from rhodecode.model.db import User, UserApiKeys, UserEmailMap
+from rhodecode.lib import auth
+from rhodecode.lib import helpers as h
+from rhodecode.model import validators
+from rhodecode.model.db import User, UserApiKeys, UserEmailMap, Repository
from rhodecode.model.meta import Session
from rhodecode.model.user import UserModel
@@ -40,6 +44,27 @@ def route_path(name, params=None, **kwar
ADMIN_PREFIX + '/users',
'users_data':
ADMIN_PREFIX + '/users_data',
+ 'users_create':
+ ADMIN_PREFIX + '/users/create',
+ 'users_new':
+ ADMIN_PREFIX + '/users/new',
+ 'user_edit':
+ ADMIN_PREFIX + '/users/{user_id}/edit',
+ 'user_edit_advanced':
+ ADMIN_PREFIX + '/users/{user_id}/edit/advanced',
+ 'user_edit_global_perms':
+ ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions',
+ 'user_edit_global_perms_update':
+ ADMIN_PREFIX + '/users/{user_id}/edit/global_permissions/update',
+ 'user_update':
+ ADMIN_PREFIX + '/users/{user_id}/update',
+ 'user_delete':
+ ADMIN_PREFIX + '/users/{user_id}/delete',
+ 'user_force_password_reset':
+ ADMIN_PREFIX + '/users/{user_id}/password_reset',
+ 'user_create_personal_repo_group':
+ ADMIN_PREFIX + '/users/{user_id}/create_repo_group',
+
'edit_user_auth_tokens':
ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
'edit_user_auth_tokens_add':
@@ -60,6 +85,15 @@ def route_path(name, params=None, **kwar
ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
'edit_user_ips_delete':
ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
+
+ 'edit_user_perms_summary':
+ ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary',
+ 'edit_user_perms_summary_json':
+ ADMIN_PREFIX + '/users/{user_id}/edit/permissions_summary/json',
+
+ 'edit_user_audit_logs':
+ ADMIN_PREFIX + '/users/{user_id}/edit/audit',
+
}[name].format(**kwargs)
if params:
@@ -220,7 +254,8 @@ class TestAdminUsersView(TestController)
def test_emails(self):
self.log_user()
user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
- response = self.app.get(route_path('edit_user_emails', user_id=user.user_id))
+ response = self.app.get(
+ route_path('edit_user_emails', user_id=user.user_id))
response.mustcontain('No additional emails specified')
def test_emails_add(self, user_util):
@@ -233,7 +268,8 @@ class TestAdminUsersView(TestController)
params={'new_email': 'example@rhodecode.com',
'csrf_token': self.csrf_token})
- response = self.app.get(route_path('edit_user_emails', user_id=user_id))
+ response = self.app.get(
+ route_path('edit_user_emails', user_id=user_id))
response.mustcontain('example@rhodecode.com')
def test_emails_add_existing_email(self, user_util, user_regular):
@@ -250,7 +286,8 @@ class TestAdminUsersView(TestController)
assert_session_flash(
response, 'This e-mail address is already taken')
- response = self.app.get(route_path('edit_user_emails', user_id=user_id))
+ response = self.app.get(
+ route_path('edit_user_emails', user_id=user_id))
response.mustcontain(no=[existing_email])
def test_emails_delete(self, user_util):
@@ -263,7 +300,8 @@ class TestAdminUsersView(TestController)
params={'new_email': 'example@rhodecode.com',
'csrf_token': self.csrf_token})
- response = self.app.get(route_path('edit_user_emails', user_id=user_id))
+ response = self.app.get(
+ route_path('edit_user_emails', user_id=user_id))
response.mustcontain('example@rhodecode.com')
user_email = UserEmailMap.query()\
@@ -277,5 +315,469 @@ class TestAdminUsersView(TestController)
params={'del_email_id': del_email_id,
'csrf_token': self.csrf_token})
- response = self.app.get(route_path('edit_user_emails', user_id=user_id))
- response.mustcontain(no=['example@rhodecode.com'])
\ No newline at end of file
+ response = self.app.get(
+ route_path('edit_user_emails', user_id=user_id))
+ response.mustcontain(no=['example@rhodecode.com'])
+
+
+ def test_create(self, request, xhr_header):
+ self.log_user()
+ username = 'newtestuser'
+ password = 'test12'
+ password_confirmation = password
+ name = 'name'
+ lastname = 'lastname'
+ email = 'mail@mail.com'
+
+ self.app.get(route_path('users_new'))
+
+ response = self.app.post(route_path('users_create'), params={
+ 'username': username,
+ 'password': password,
+ 'password_confirmation': password_confirmation,
+ 'firstname': name,
+ 'active': True,
+ 'lastname': lastname,
+ 'extern_name': 'rhodecode',
+ 'extern_type': 'rhodecode',
+ 'email': email,
+ 'csrf_token': self.csrf_token,
+ })
+ user_link = h.link_to(
+ username,
+ route_path(
+ 'user_edit', user_id=User.get_by_username(username).user_id))
+ assert_session_flash(response, 'Created user %s' % (user_link,))
+
+ @request.addfinalizer
+ def cleanup():
+ fixture.destroy_user(username)
+ Session().commit()
+
+ new_user = User.query().filter(User.username == username).one()
+
+ assert new_user.username == username
+ assert auth.check_password(password, new_user.password)
+ assert new_user.name == name
+ assert new_user.lastname == lastname
+ assert new_user.email == email
+
+ response = self.app.get(route_path('users_data'),
+ extra_environ=xhr_header)
+ response.mustcontain(username)
+
+ def test_create_err(self):
+ self.log_user()
+ username = 'new_user'
+ password = ''
+ name = 'name'
+ lastname = 'lastname'
+ email = 'errmail.com'
+
+ self.app.get(route_path('users_new'))
+
+ response = self.app.post(route_path('users_create'), params={
+ 'username': username,
+ 'password': password,
+ 'name': name,
+ 'active': False,
+ 'lastname': lastname,
+ 'email': email,
+ 'csrf_token': self.csrf_token,
+ })
+
+ msg = validators.ValidUsername(
+ False, {})._messages['system_invalid_username']
+ msg = h.html_escape(msg % {'username': 'new_user'})
+ response.mustcontain('%s' % msg)
+ response.mustcontain(
+ 'Please enter a value')
+ response.mustcontain(
+ 'An email address must contain a'
+ ' single @')
+
+ def get_user():
+ Session().query(User).filter(User.username == username).one()
+
+ with pytest.raises(NoResultFound):
+ get_user()
+
+ def test_new(self):
+ self.log_user()
+ self.app.get(route_path('users_new'))
+
+ @pytest.mark.parametrize("name, attrs", [
+ ('firstname', {'firstname': 'new_username'}),
+ ('lastname', {'lastname': 'new_username'}),
+ ('admin', {'admin': True}),
+ ('admin', {'admin': False}),
+ ('extern_type', {'extern_type': 'ldap'}),
+ ('extern_type', {'extern_type': None}),
+ ('extern_name', {'extern_name': 'test'}),
+ ('extern_name', {'extern_name': None}),
+ ('active', {'active': False}),
+ ('active', {'active': True}),
+ ('email', {'email': 'some@email.com'}),
+ ('language', {'language': 'de'}),
+ ('language', {'language': 'en'}),
+ # ('new_password', {'new_password': 'foobar123',
+ # 'password_confirmation': 'foobar123'})
+ ])
+ def test_update(self, name, attrs, user_util):
+ self.log_user()
+ usr = user_util.create_user(
+ password='qweqwe',
+ email='testme@rhodecode.org',
+ extern_type='rhodecode',
+ extern_name='xxx',
+ )
+ user_id = usr.user_id
+ Session().commit()
+
+ params = usr.get_api_data()
+ cur_lang = params['language'] or 'en'
+ params.update({
+ 'password_confirmation': '',
+ 'new_password': '',
+ 'language': cur_lang,
+ 'csrf_token': self.csrf_token,
+ })
+ params.update({'new_password': ''})
+ params.update(attrs)
+ if name == 'email':
+ params['emails'] = [attrs['email']]
+ elif name == 'extern_type':
+ # cannot update this via form, expected value is original one
+ params['extern_type'] = "rhodecode"
+ elif name == 'extern_name':
+ # cannot update this via form, expected value is original one
+ params['extern_name'] = 'xxx'
+ # special case since this user is not
+ # logged in yet his data is not filled
+ # so we use creation data
+
+ response = self.app.post(
+ route_path('user_update', user_id=usr.user_id), params)
+ assert response.status_int == 302
+ assert_session_flash(response, 'User updated successfully')
+
+ updated_user = User.get(user_id)
+ updated_params = updated_user.get_api_data()
+ updated_params.update({'password_confirmation': ''})
+ updated_params.update({'new_password': ''})
+
+ del params['csrf_token']
+ assert params == updated_params
+
+ def test_update_and_migrate_password(
+ self, autologin_user, real_crypto_backend, user_util):
+
+ user = user_util.create_user()
+ temp_user = user.username
+ user.password = auth._RhodeCodeCryptoSha256().hash_create(
+ b'test123')
+ Session().add(user)
+ Session().commit()
+
+ params = user.get_api_data()
+
+ params.update({
+ 'password_confirmation': 'qweqwe123',
+ 'new_password': 'qweqwe123',
+ 'language': 'en',
+ 'csrf_token': autologin_user.csrf_token,
+ })
+
+ response = self.app.post(
+ route_path('user_update', user_id=user.user_id), params)
+ assert response.status_int == 302
+ assert_session_flash(response, 'User updated successfully')
+
+ # new password should be bcrypted, after log-in and transfer
+ user = User.get_by_username(temp_user)
+ assert user.password.startswith('$')
+
+ updated_user = User.get_by_username(temp_user)
+ updated_params = updated_user.get_api_data()
+ updated_params.update({'password_confirmation': 'qweqwe123'})
+ updated_params.update({'new_password': 'qweqwe123'})
+
+ del params['csrf_token']
+ assert params == updated_params
+
+ def test_delete(self):
+ self.log_user()
+ username = 'newtestuserdeleteme'
+
+ fixture.create_user(name=username)
+
+ new_user = Session().query(User)\
+ .filter(User.username == username).one()
+ response = self.app.post(
+ route_path('user_delete', user_id=new_user.user_id),
+ params={'csrf_token': self.csrf_token})
+
+ assert_session_flash(response, 'Successfully deleted user')
+
+ def test_delete_owner_of_repository(self, request, user_util):
+ self.log_user()
+ obj_name = 'test_repo'
+ usr = user_util.create_user()
+ username = usr.username
+ fixture.create_repo(obj_name, cur_user=usr.username)
+
+ new_user = Session().query(User)\
+ .filter(User.username == username).one()
+ response = self.app.post(
+ route_path('user_delete', user_id=new_user.user_id),
+ params={'csrf_token': self.csrf_token})
+
+ msg = 'user "%s" still owns 1 repositories and cannot be removed. ' \
+ 'Switch owners or remove those repositories:%s' % (username,
+ obj_name)
+ assert_session_flash(response, msg)
+ fixture.destroy_repo(obj_name)
+
+ def test_delete_owner_of_repository_detaching(self, request, user_util):
+ self.log_user()
+ obj_name = 'test_repo'
+ usr = user_util.create_user(auto_cleanup=False)
+ username = usr.username
+ fixture.create_repo(obj_name, cur_user=usr.username)
+
+ new_user = Session().query(User)\
+ .filter(User.username == username).one()
+ response = self.app.post(
+ route_path('user_delete', user_id=new_user.user_id),
+ params={'user_repos': 'detach', 'csrf_token': self.csrf_token})
+
+ msg = 'Detached 1 repositories'
+ assert_session_flash(response, msg)
+ fixture.destroy_repo(obj_name)
+
+ def test_delete_owner_of_repository_deleting(self, request, user_util):
+ self.log_user()
+ obj_name = 'test_repo'
+ usr = user_util.create_user(auto_cleanup=False)
+ username = usr.username
+ fixture.create_repo(obj_name, cur_user=usr.username)
+
+ new_user = Session().query(User)\
+ .filter(User.username == username).one()
+ response = self.app.post(
+ route_path('user_delete', user_id=new_user.user_id),
+ params={'user_repos': 'delete', 'csrf_token': self.csrf_token})
+
+ msg = 'Deleted 1 repositories'
+ assert_session_flash(response, msg)
+
+ def test_delete_owner_of_repository_group(self, request, user_util):
+ self.log_user()
+ obj_name = 'test_group'
+ usr = user_util.create_user()
+ username = usr.username
+ fixture.create_repo_group(obj_name, cur_user=usr.username)
+
+ new_user = Session().query(User)\
+ .filter(User.username == username).one()
+ response = self.app.post(
+ route_path('user_delete', user_id=new_user.user_id),
+ params={'csrf_token': self.csrf_token})
+
+ msg = 'user "%s" still owns 1 repository groups and cannot be removed. ' \
+ 'Switch owners or remove those repository groups:%s' % (username,
+ obj_name)
+ assert_session_flash(response, msg)
+ fixture.destroy_repo_group(obj_name)
+
+ def test_delete_owner_of_repository_group_detaching(self, request, user_util):
+ self.log_user()
+ obj_name = 'test_group'
+ usr = user_util.create_user(auto_cleanup=False)
+ username = usr.username
+ fixture.create_repo_group(obj_name, cur_user=usr.username)
+
+ new_user = Session().query(User)\
+ .filter(User.username == username).one()
+ response = self.app.post(
+ route_path('user_delete', user_id=new_user.user_id),
+ params={'user_repo_groups': 'delete', 'csrf_token': self.csrf_token})
+
+ msg = 'Deleted 1 repository groups'
+ assert_session_flash(response, msg)
+
+ def test_delete_owner_of_repository_group_deleting(self, request, user_util):
+ self.log_user()
+ obj_name = 'test_group'
+ usr = user_util.create_user(auto_cleanup=False)
+ username = usr.username
+ fixture.create_repo_group(obj_name, cur_user=usr.username)
+
+ new_user = Session().query(User)\
+ .filter(User.username == username).one()
+ response = self.app.post(
+ route_path('user_delete', user_id=new_user.user_id),
+ params={'user_repo_groups': 'detach', 'csrf_token': self.csrf_token})
+
+ msg = 'Detached 1 repository groups'
+ assert_session_flash(response, msg)
+ fixture.destroy_repo_group(obj_name)
+
+ def test_delete_owner_of_user_group(self, request, user_util):
+ self.log_user()
+ obj_name = 'test_user_group'
+ usr = user_util.create_user()
+ username = usr.username
+ fixture.create_user_group(obj_name, cur_user=usr.username)
+
+ new_user = Session().query(User)\
+ .filter(User.username == username).one()
+ response = self.app.post(
+ route_path('user_delete', user_id=new_user.user_id),
+ params={'csrf_token': self.csrf_token})
+
+ msg = 'user "%s" still owns 1 user groups and cannot be removed. ' \
+ 'Switch owners or remove those user groups:%s' % (username,
+ obj_name)
+ assert_session_flash(response, msg)
+ fixture.destroy_user_group(obj_name)
+
+ def test_delete_owner_of_user_group_detaching(self, request, user_util):
+ self.log_user()
+ obj_name = 'test_user_group'
+ usr = user_util.create_user(auto_cleanup=False)
+ username = usr.username
+ fixture.create_user_group(obj_name, cur_user=usr.username)
+
+ new_user = Session().query(User)\
+ .filter(User.username == username).one()
+ try:
+ response = self.app.post(
+ route_path('user_delete', user_id=new_user.user_id),
+ params={'user_user_groups': 'detach',
+ 'csrf_token': self.csrf_token})
+
+ msg = 'Detached 1 user groups'
+ assert_session_flash(response, msg)
+ finally:
+ fixture.destroy_user_group(obj_name)
+
+ def test_delete_owner_of_user_group_deleting(self, request, user_util):
+ self.log_user()
+ obj_name = 'test_user_group'
+ usr = user_util.create_user(auto_cleanup=False)
+ username = usr.username
+ fixture.create_user_group(obj_name, cur_user=usr.username)
+
+ new_user = Session().query(User)\
+ .filter(User.username == username).one()
+ response = self.app.post(
+ route_path('user_delete', user_id=new_user.user_id),
+ params={'user_user_groups': 'delete', 'csrf_token': self.csrf_token})
+
+ msg = 'Deleted 1 user groups'
+ assert_session_flash(response, msg)
+
+ def test_edit(self, user_util):
+ self.log_user()
+ user = user_util.create_user()
+ self.app.get(route_path('user_edit', user_id=user.user_id))
+
+ def test_edit_default_user_redirect(self):
+ self.log_user()
+ user = User.get_default_user()
+ self.app.get(route_path('user_edit', user_id=user.user_id), status=302)
+
+ @pytest.mark.parametrize(
+ 'repo_create, repo_create_write, user_group_create, repo_group_create,'
+ 'fork_create, inherit_default_permissions, expect_error,'
+ 'expect_form_error', [
+ ('hg.create.none', 'hg.create.write_on_repogroup.false',
+ 'hg.usergroup.create.false', 'hg.repogroup.create.false',
+ 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
+ ('hg.create.repository', 'hg.create.write_on_repogroup.false',
+ 'hg.usergroup.create.false', 'hg.repogroup.create.false',
+ 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
+ ('hg.create.repository', 'hg.create.write_on_repogroup.true',
+ 'hg.usergroup.create.true', 'hg.repogroup.create.true',
+ 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
+ False),
+ ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
+ 'hg.usergroup.create.true', 'hg.repogroup.create.true',
+ 'hg.fork.repository', 'hg.inherit_default_perms.false', False,
+ True),
+ ('', '', '', '', '', '', True, False),
+ ])
+ def test_global_perms_on_user(
+ self, repo_create, repo_create_write, user_group_create,
+ repo_group_create, fork_create, expect_error, expect_form_error,
+ inherit_default_permissions, user_util):
+ self.log_user()
+ user = user_util.create_user()
+ uid = user.user_id
+
+ # ENABLE REPO CREATE ON A GROUP
+ perm_params = {
+ 'inherit_default_permissions': False,
+ 'default_repo_create': repo_create,
+ 'default_repo_create_on_write': repo_create_write,
+ 'default_user_group_create': user_group_create,
+ 'default_repo_group_create': repo_group_create,
+ 'default_fork_create': fork_create,
+ 'default_inherit_default_permissions': inherit_default_permissions,
+ 'csrf_token': self.csrf_token,
+ }
+ response = self.app.post(
+ route_path('user_edit_global_perms_update', user_id=uid),
+ params=perm_params)
+
+ if expect_form_error:
+ assert response.status_int == 200
+ response.mustcontain('Value must be one of')
+ else:
+ if expect_error:
+ msg = 'An error occurred during permissions saving'
+ else:
+ msg = 'User global permissions updated successfully'
+ ug = User.get(uid)
+ del perm_params['inherit_default_permissions']
+ del perm_params['csrf_token']
+ assert perm_params == ug.get_default_perms()
+ assert_session_flash(response, msg)
+
+ def test_global_permissions_initial_values(self, user_util):
+ self.log_user()
+ user = user_util.create_user()
+ uid = user.user_id
+ response = self.app.get(
+ route_path('user_edit_global_perms', user_id=uid))
+ default_user = User.get_default_user()
+ default_permissions = default_user.get_default_perms()
+ assert_response = response.assert_response()
+ expected_permissions = (
+ 'default_repo_create', 'default_repo_create_on_write',
+ 'default_fork_create', 'default_repo_group_create',
+ 'default_user_group_create', 'default_inherit_default_permissions')
+ for permission in expected_permissions:
+ css_selector = '[name={}][checked=checked]'.format(permission)
+ element = assert_response.get_element(css_selector)
+ assert element.value == default_permissions[permission]
+
+ def test_perms_summary_page(self):
+ user = self.log_user()
+ response = self.app.get(
+ route_path('edit_user_perms_summary', user_id=user['user_id']))
+ for repo in Repository.query().all():
+ response.mustcontain(repo.repo_name)
+
+ def test_perms_summary_page_json(self):
+ user = self.log_user()
+ response = self.app.get(
+ route_path('edit_user_perms_summary_json', user_id=user['user_id']))
+ for repo in Repository.query().all():
+ response.mustcontain(repo.repo_name)
+
+ def test_audit_log_page(self):
+ user = self.log_user()
+ self.app.get(
+ route_path('edit_user_audit_logs', user_id=user['user_id']))
diff --git a/rhodecode/apps/admin/views/users.py b/rhodecode/apps/admin/views/users.py
--- a/rhodecode/apps/admin/views/users.py
+++ b/rhodecode/apps/admin/views/users.py
@@ -18,7 +18,6 @@
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
-import time
import logging
import datetime
import formencode
@@ -26,51 +25,46 @@ import formencode.htmlfill
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
-from sqlalchemy.sql.functions import coalesce
-from sqlalchemy.exc import IntegrityError
+from pyramid.renderers import render
+from pyramid.response import Response
-from rhodecode.apps._base import BaseAppView, DataGridAppView
+from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
+from rhodecode.authentication.plugins import auth_rhodecode
from rhodecode.events import trigger
from rhodecode.lib import audit_logger
+from rhodecode.lib.exceptions import (
+ UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
+ UserOwnsUserGroupsException, DefaultUserException)
from rhodecode.lib.ext_json import json
from rhodecode.lib.auth import (
LoginRequired, HasPermissionAllDecorator, CSRFRequired)
from rhodecode.lib import helpers as h
-from rhodecode.lib.utils2 import safe_int, safe_unicode
+from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
from rhodecode.model.auth_token import AuthTokenModel
+from rhodecode.model.forms import (
+ UserForm, UserIndividualPermissionsForm, UserPermissionsForm)
+from rhodecode.model.permission import PermissionModel
+from rhodecode.model.repo_group import RepoGroupModel
from rhodecode.model.ssh_key import SshKeyModel
from rhodecode.model.user import UserModel
from rhodecode.model.user_group import UserGroupModel
from rhodecode.model.db import (
- or_, User, UserIpMap, UserEmailMap, UserApiKeys, UserSshKeys)
+ or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
+ UserApiKeys, UserSshKeys, RepoGroup)
from rhodecode.model.meta import Session
log = logging.getLogger(__name__)
class AdminUsersView(BaseAppView, DataGridAppView):
- ALLOW_SCOPED_TOKENS = False
- """
- This view has alternative version inside EE, if modified please take a look
- in there as well.
- """
def load_default_context(self):
c = self._get_local_tmpl_context()
- c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
self._register_global_c(c)
return c
- def _redirect_for_default_user(self, username):
- _ = self.request.translate
- if username == User.DEFAULT_USER:
- h.flash(_("You can't edit this user"), category='warning')
- # TODO(marcink): redirect to 'users' admin panel once this
- # is a pyramid view
- raise HTTPFound('/')
-
@LoginRequired()
@HasPermissionAllDecorator('hg.admin')
@view_config(
@@ -163,6 +157,529 @@ class AdminUsersView(BaseAppView, DataGr
return data
+ def _set_personal_repo_group_template_vars(self, c_obj):
+ DummyUser = AttributeDict({
+ 'username': '${username}',
+ 'user_id': '${user_id}',
+ })
+ c_obj.default_create_repo_group = RepoGroupModel() \
+ .get_default_create_personal_repo_group()
+ c_obj.personal_repo_group_name = RepoGroupModel() \
+ .get_personal_group_name(DummyUser)
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @view_config(
+ route_name='users_new', request_method='GET',
+ renderer='rhodecode:templates/admin/users/user_add.mako')
+ def users_new(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+ c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
+ self._set_personal_repo_group_template_vars(c)
+ return self._get_template_context(c)
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @CSRFRequired()
+ @view_config(
+ route_name='users_create', request_method='POST',
+ renderer='rhodecode:templates/admin/users/user_add.mako')
+ def users_create(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+ c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
+ user_model = UserModel()
+ user_form = UserForm()()
+ try:
+ form_result = user_form.to_python(dict(self.request.POST))
+ user = user_model.create(form_result)
+ Session().flush()
+ creation_data = user.get_api_data()
+ username = form_result['username']
+
+ audit_logger.store_web(
+ 'user.create', action_data={'data': creation_data},
+ user=c.rhodecode_user)
+
+ user_link = h.link_to(
+ h.escape(username),
+ h.route_path('user_edit', user_id=user.user_id))
+ h.flash(h.literal(_('Created user %(user_link)s')
+ % {'user_link': user_link}), category='success')
+ Session().commit()
+ except formencode.Invalid as errors:
+ self._set_personal_repo_group_template_vars(c)
+ data = render(
+ 'rhodecode:templates/admin/users/user_add.mako',
+ self._get_template_context(c), self.request)
+ html = formencode.htmlfill.render(
+ data,
+ defaults=errors.value,
+ errors=errors.error_dict or {},
+ prefix_error=False,
+ encoding="UTF-8",
+ force_defaults=False
+ )
+ return Response(html)
+ except UserCreationError as e:
+ h.flash(e, 'error')
+ except Exception:
+ log.exception("Exception creation of user")
+ h.flash(_('Error occurred during creation of user %s')
+ % self.request.POST.get('username'), category='error')
+ raise HTTPFound(h.route_path('users'))
+
+
+class UsersView(UserAppView):
+ ALLOW_SCOPED_TOKENS = False
+ """
+ This view has alternative version inside EE, if modified please take a look
+ in there as well.
+ """
+
+ def load_default_context(self):
+ c = self._get_local_tmpl_context()
+ c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
+ c.allowed_languages = [
+ ('en', 'English (en)'),
+ ('de', 'German (de)'),
+ ('fr', 'French (fr)'),
+ ('it', 'Italian (it)'),
+ ('ja', 'Japanese (ja)'),
+ ('pl', 'Polish (pl)'),
+ ('pt', 'Portuguese (pt)'),
+ ('ru', 'Russian (ru)'),
+ ('zh', 'Chinese (zh)'),
+ ]
+ req = self.request
+
+ c.available_permissions = req.registry.settings['available_permissions']
+ PermissionModel().set_global_permission_choices(
+ c, gettext_translator=req.translate)
+
+ self._register_global_c(c)
+ return c
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @CSRFRequired()
+ @view_config(
+ route_name='user_update', request_method='POST',
+ renderer='rhodecode:templates/admin/users/user_edit.mako')
+ def user_update(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+
+ user_id = self.db_user_id
+ c.user = self.db_user
+
+ c.active = 'profile'
+ c.extern_type = c.user.extern_type
+ c.extern_name = c.user.extern_name
+ c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
+ available_languages = [x[0] for x in c.allowed_languages]
+ _form = UserForm(edit=True, available_languages=available_languages,
+ old_data={'user_id': user_id,
+ 'email': c.user.email})()
+ form_result = {}
+ old_values = c.user.get_api_data()
+ try:
+ form_result = _form.to_python(dict(self.request.POST))
+ skip_attrs = ['extern_type', 'extern_name']
+ # TODO: plugin should define if username can be updated
+ if c.extern_type != "rhodecode":
+ # forbid updating username for external accounts
+ skip_attrs.append('username')
+
+ UserModel().update_user(
+ user_id, skip_attrs=skip_attrs, **form_result)
+
+ audit_logger.store_web(
+ 'user.edit', action_data={'old_data': old_values},
+ user=c.rhodecode_user)
+
+ Session().commit()
+ h.flash(_('User updated successfully'), category='success')
+ except formencode.Invalid as errors:
+ data = render(
+ 'rhodecode:templates/admin/users/user_edit.mako',
+ self._get_template_context(c), self.request)
+ html = formencode.htmlfill.render(
+ data,
+ defaults=errors.value,
+ errors=errors.error_dict or {},
+ prefix_error=False,
+ encoding="UTF-8",
+ force_defaults=False
+ )
+ return Response(html)
+ except UserCreationError as e:
+ h.flash(e, 'error')
+ except Exception:
+ log.exception("Exception updating user")
+ h.flash(_('Error occurred during update of user %s')
+ % form_result.get('username'), category='error')
+ raise HTTPFound(h.route_path('user_edit', user_id=user_id))
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @CSRFRequired()
+ @view_config(
+ route_name='user_delete', request_method='POST',
+ renderer='rhodecode:templates/admin/users/user_edit.mako')
+ def user_delete(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+ c.user = self.db_user
+
+ _repos = c.user.repositories
+ _repo_groups = c.user.repository_groups
+ _user_groups = c.user.user_groups
+
+ handle_repos = None
+ handle_repo_groups = None
+ handle_user_groups = None
+ # dummy call for flash of handle
+ set_handle_flash_repos = lambda: None
+ set_handle_flash_repo_groups = lambda: None
+ set_handle_flash_user_groups = lambda: None
+
+ if _repos and self.request.POST.get('user_repos'):
+ do = self.request.POST['user_repos']
+ if do == 'detach':
+ handle_repos = 'detach'
+ set_handle_flash_repos = lambda: h.flash(
+ _('Detached %s repositories') % len(_repos),
+ category='success')
+ elif do == 'delete':
+ handle_repos = 'delete'
+ set_handle_flash_repos = lambda: h.flash(
+ _('Deleted %s repositories') % len(_repos),
+ category='success')
+
+ if _repo_groups and self.request.POST.get('user_repo_groups'):
+ do = self.request.POST['user_repo_groups']
+ if do == 'detach':
+ handle_repo_groups = 'detach'
+ set_handle_flash_repo_groups = lambda: h.flash(
+ _('Detached %s repository groups') % len(_repo_groups),
+ category='success')
+ elif do == 'delete':
+ handle_repo_groups = 'delete'
+ set_handle_flash_repo_groups = lambda: h.flash(
+ _('Deleted %s repository groups') % len(_repo_groups),
+ category='success')
+
+ if _user_groups and self.request.POST.get('user_user_groups'):
+ do = self.request.POST['user_user_groups']
+ if do == 'detach':
+ handle_user_groups = 'detach'
+ set_handle_flash_user_groups = lambda: h.flash(
+ _('Detached %s user groups') % len(_user_groups),
+ category='success')
+ elif do == 'delete':
+ handle_user_groups = 'delete'
+ set_handle_flash_user_groups = lambda: h.flash(
+ _('Deleted %s user groups') % len(_user_groups),
+ category='success')
+
+ old_values = c.user.get_api_data()
+ try:
+ UserModel().delete(c.user, handle_repos=handle_repos,
+ handle_repo_groups=handle_repo_groups,
+ handle_user_groups=handle_user_groups)
+
+ audit_logger.store_web(
+ 'user.delete', action_data={'old_data': old_values},
+ user=c.rhodecode_user)
+
+ Session().commit()
+ set_handle_flash_repos()
+ set_handle_flash_repo_groups()
+ set_handle_flash_user_groups()
+ h.flash(_('Successfully deleted user'), category='success')
+ except (UserOwnsReposException, UserOwnsRepoGroupsException,
+ UserOwnsUserGroupsException, DefaultUserException) as e:
+ h.flash(e, category='warning')
+ except Exception:
+ log.exception("Exception during deletion of user")
+ h.flash(_('An error occurred during deletion of user'),
+ category='error')
+ raise HTTPFound(h.route_path('users'))
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @view_config(
+ route_name='user_edit', request_method='GET',
+ renderer='rhodecode:templates/admin/users/user_edit.mako')
+ def user_edit(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+ c.user = self.db_user
+
+ c.active = 'profile'
+ c.extern_type = c.user.extern_type
+ c.extern_name = c.user.extern_name
+ c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
+
+ defaults = c.user.get_dict()
+ defaults.update({'language': c.user.user_data.get('language')})
+
+ data = render(
+ 'rhodecode:templates/admin/users/user_edit.mako',
+ self._get_template_context(c), self.request)
+ html = formencode.htmlfill.render(
+ data,
+ defaults=defaults,
+ encoding="UTF-8",
+ force_defaults=False
+ )
+ return Response(html)
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @view_config(
+ route_name='user_edit_advanced', request_method='GET',
+ renderer='rhodecode:templates/admin/users/user_edit.mako')
+ def user_edit_advanced(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+
+ user_id = self.db_user_id
+ c.user = self.db_user
+
+ c.active = 'advanced'
+ c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
+ c.personal_repo_group_name = RepoGroupModel()\
+ .get_personal_group_name(c.user)
+
+ c.user_to_review_rules = sorted(
+ (x.user for x in c.user.user_review_rules),
+ key=lambda u: u.username.lower())
+
+ c.first_admin = User.get_first_super_admin()
+ defaults = c.user.get_dict()
+
+ # Interim workaround if the user participated on any pull requests as a
+ # reviewer.
+ has_review = len(c.user.reviewer_pull_requests)
+ c.can_delete_user = not has_review
+ c.can_delete_user_message = ''
+ inactive_link = h.link_to(
+ 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
+ if has_review == 1:
+ c.can_delete_user_message = h.literal(_(
+ 'The user participates as reviewer in {} pull request and '
+ 'cannot be deleted. \nYou can set the user to '
+ '"{}" instead of deleting it.').format(
+ has_review, inactive_link))
+ elif has_review:
+ c.can_delete_user_message = h.literal(_(
+ 'The user participates as reviewer in {} pull requests and '
+ 'cannot be deleted. \nYou can set the user to '
+ '"{}" instead of deleting it.').format(
+ has_review, inactive_link))
+
+ data = render(
+ 'rhodecode:templates/admin/users/user_edit.mako',
+ self._get_template_context(c), self.request)
+ html = formencode.htmlfill.render(
+ data,
+ defaults=defaults,
+ encoding="UTF-8",
+ force_defaults=False
+ )
+ return Response(html)
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @view_config(
+ route_name='user_edit_global_perms', request_method='GET',
+ renderer='rhodecode:templates/admin/users/user_edit.mako')
+ def user_edit_global_perms(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+ c.user = self.db_user
+
+ c.active = 'global_perms'
+
+ c.default_user = User.get_default_user()
+ defaults = c.user.get_dict()
+ defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
+ defaults.update(c.default_user.get_default_perms())
+ defaults.update(c.user.get_default_perms())
+
+ data = render(
+ 'rhodecode:templates/admin/users/user_edit.mako',
+ self._get_template_context(c), self.request)
+ html = formencode.htmlfill.render(
+ data,
+ defaults=defaults,
+ encoding="UTF-8",
+ force_defaults=False
+ )
+ return Response(html)
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @CSRFRequired()
+ @view_config(
+ route_name='user_edit_global_perms_update', request_method='POST',
+ renderer='rhodecode:templates/admin/users/user_edit.mako')
+ def user_edit_global_perms_update(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+
+ user_id = self.db_user_id
+ c.user = self.db_user
+
+ c.active = 'global_perms'
+ try:
+ # first stage that verifies the checkbox
+ _form = UserIndividualPermissionsForm()
+ form_result = _form.to_python(dict(self.request.POST))
+ inherit_perms = form_result['inherit_default_permissions']
+ c.user.inherit_default_permissions = inherit_perms
+ Session().add(c.user)
+
+ if not inherit_perms:
+ # only update the individual ones if we un check the flag
+ _form = UserPermissionsForm(
+ [x[0] for x in c.repo_create_choices],
+ [x[0] for x in c.repo_create_on_write_choices],
+ [x[0] for x in c.repo_group_create_choices],
+ [x[0] for x in c.user_group_create_choices],
+ [x[0] for x in c.fork_choices],
+ [x[0] for x in c.inherit_default_permission_choices])()
+
+ form_result = _form.to_python(dict(self.request.POST))
+ form_result.update({'perm_user_id': c.user.user_id})
+
+ PermissionModel().update_user_permissions(form_result)
+
+ # TODO(marcink): implement global permissions
+ # audit_log.store_web('user.edit.permissions')
+
+ Session().commit()
+ h.flash(_('User global permissions updated successfully'),
+ category='success')
+
+ except formencode.Invalid as errors:
+ data = render(
+ 'rhodecode:templates/admin/users/user_edit.mako',
+ self._get_template_context(c), self.request)
+ html = formencode.htmlfill.render(
+ data,
+ defaults=errors.value,
+ errors=errors.error_dict or {},
+ prefix_error=False,
+ encoding="UTF-8",
+ force_defaults=False
+ )
+ return Response(html)
+ except Exception:
+ log.exception("Exception during permissions saving")
+ h.flash(_('An error occurred during permissions saving'),
+ category='error')
+ raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @CSRFRequired()
+ @view_config(
+ route_name='user_force_password_reset', request_method='POST',
+ renderer='rhodecode:templates/admin/users/user_edit.mako')
+ def user_force_password_reset(self):
+ """
+ toggle reset password flag for this user
+ """
+ _ = self.request.translate
+ c = self.load_default_context()
+
+ user_id = self.db_user_id
+ c.user = self.db_user
+
+ try:
+ old_value = c.user.user_data.get('force_password_change')
+ c.user.update_userdata(force_password_change=not old_value)
+
+ if old_value:
+ msg = _('Force password change disabled for user')
+ audit_logger.store_web(
+ 'user.edit.password_reset.disabled',
+ user=c.rhodecode_user)
+ else:
+ msg = _('Force password change enabled for user')
+ audit_logger.store_web(
+ 'user.edit.password_reset.enabled',
+ user=c.rhodecode_user)
+
+ Session().commit()
+ h.flash(msg, category='success')
+ except Exception:
+ log.exception("Exception during password reset for user")
+ h.flash(_('An error occurred during password reset for user'),
+ category='error')
+
+ raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @CSRFRequired()
+ @view_config(
+ route_name='user_create_personal_repo_group', request_method='POST',
+ renderer='rhodecode:templates/admin/users/user_edit.mako')
+ def user_create_personal_repo_group(self):
+ """
+ Create personal repository group for this user
+ """
+ from rhodecode.model.repo_group import RepoGroupModel
+
+ _ = self.request.translate
+ c = self.load_default_context()
+
+ user_id = self.db_user_id
+ c.user = self.db_user
+
+ personal_repo_group = RepoGroup.get_user_personal_repo_group(
+ c.user.user_id)
+ if personal_repo_group:
+ raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
+
+ personal_repo_group_name = RepoGroupModel().get_personal_group_name(
+ c.user)
+ named_personal_group = RepoGroup.get_by_group_name(
+ personal_repo_group_name)
+ try:
+
+ if named_personal_group and named_personal_group.user_id == c.user.user_id:
+ # migrate the same named group, and mark it as personal
+ named_personal_group.personal = True
+ Session().add(named_personal_group)
+ Session().commit()
+ msg = _('Linked repository group `%s` as personal' % (
+ personal_repo_group_name,))
+ h.flash(msg, category='success')
+ elif not named_personal_group:
+ RepoGroupModel().create_personal_repo_group(c.user)
+
+ msg = _('Created repository group `%s`' % (
+ personal_repo_group_name,))
+ h.flash(msg, category='success')
+ else:
+ msg = _('Repository group `%s` is already taken' % (
+ personal_repo_group_name,))
+ h.flash(msg, category='warning')
+ except Exception:
+ log.exception("Exception during repository group creation")
+ msg = _(
+ 'An error occurred during repository group creation for user')
+ h.flash(msg, category='error')
+ Session().rollback()
+
+ raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
+
@LoginRequired()
@HasPermissionAllDecorator('hg.admin')
@view_config(
@@ -171,10 +688,7 @@ class AdminUsersView(BaseAppView, DataGr
def auth_tokens(self):
_ = self.request.translate
c = self.load_default_context()
-
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ c.user = self.db_user
c.active = 'auth_tokens'
@@ -200,10 +714,8 @@ class AdminUsersView(BaseAppView, DataGr
_ = self.request.translate
c = self.load_default_context()
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
-
- self._redirect_for_default_user(c.user.username)
+ user_id = self.db_user_id
+ c.user = self.db_user
user_data = c.user.get_api_data()
lifetime = safe_int(self.request.POST.get('lifetime'), -1)
@@ -233,9 +745,9 @@ class AdminUsersView(BaseAppView, DataGr
_ = self.request.translate
c = self.load_default_context()
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ user_id = self.db_user_id
+ c.user = self.db_user
+
user_data = c.user.get_api_data()
del_auth_token = self.request.POST.get('del_auth_token')
@@ -262,10 +774,7 @@ class AdminUsersView(BaseAppView, DataGr
def ssh_keys(self):
_ = self.request.translate
c = self.load_default_context()
-
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ c.user = self.db_user
c.active = 'ssh_keys'
c.default_key = self.request.GET.get('default_key')
@@ -281,9 +790,7 @@ class AdminUsersView(BaseAppView, DataGr
_ = self.request.translate
c = self.load_default_context()
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ c.user = self.db_user
c.active = 'ssh_keys_generate'
comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
@@ -300,10 +807,8 @@ class AdminUsersView(BaseAppView, DataGr
_ = self.request.translate
c = self.load_default_context()
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
-
- self._redirect_for_default_user(c.user.username)
+ user_id = self.db_user_id
+ c.user = self.db_user
user_data = c.user.get_api_data()
key_data = self.request.POST.get('key_data')
@@ -353,9 +858,9 @@ class AdminUsersView(BaseAppView, DataGr
_ = self.request.translate
c = self.load_default_context()
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ user_id = self.db_user_id
+ c.user = self.db_user
+
user_data = c.user.get_api_data()
del_ssh_key = self.request.POST.get('del_ssh_key')
@@ -384,10 +889,7 @@ class AdminUsersView(BaseAppView, DataGr
def emails(self):
_ = self.request.translate
c = self.load_default_context()
-
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ c.user = self.db_user
c.active = 'emails'
c.user_email_map = UserEmailMap.query() \
@@ -404,22 +906,26 @@ class AdminUsersView(BaseAppView, DataGr
_ = self.request.translate
c = self.load_default_context()
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ user_id = self.db_user_id
+ c.user = self.db_user
email = self.request.POST.get('new_email')
user_data = c.user.get_api_data()
try:
UserModel().add_extra_email(c.user.user_id, email)
audit_logger.store_web(
- 'user.edit.email.add', action_data={'email': email, 'user': user_data},
+ 'user.edit.email.add',
+ action_data={'email': email, 'user': user_data},
user=self._rhodecode_user)
Session().commit()
h.flash(_("Added new email address `%s` for user account") % email,
category='success')
except formencode.Invalid as error:
h.flash(h.escape(error.error_dict['email']), category='error')
+ except IntegrityError:
+ log.warning("Email %s already exists", email)
+ h.flash(_('Email `{}` is already registered for another user.').format(email),
+ category='error')
except Exception:
log.exception("Exception during email saving")
h.flash(_('An error occurred during email saving'),
@@ -435,9 +941,8 @@ class AdminUsersView(BaseAppView, DataGr
_ = self.request.translate
c = self.load_default_context()
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ user_id = self.db_user_id
+ c.user = self.db_user
email_id = self.request.POST.get('del_email_id')
user_model = UserModel()
@@ -446,7 +951,8 @@ class AdminUsersView(BaseAppView, DataGr
user_data = c.user.get_api_data()
user_model.delete_extra_email(c.user.user_id, email_id)
audit_logger.store_web(
- 'user.edit.email.delete', action_data={'email': email, 'user': user_data},
+ 'user.edit.email.delete',
+ action_data={'email': email, 'user': user_data},
user=self._rhodecode_user)
Session().commit()
h.flash(_("Removed email address from user account"),
@@ -461,10 +967,7 @@ class AdminUsersView(BaseAppView, DataGr
def ips(self):
_ = self.request.translate
c = self.load_default_context()
-
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ c.user = self.db_user
c.active = 'ips'
c.user_ip_map = UserIpMap.query() \
@@ -481,14 +984,14 @@ class AdminUsersView(BaseAppView, DataGr
@CSRFRequired()
@view_config(
route_name='edit_user_ips_add', request_method='POST')
+ # NOTE(marcink): this view is allowed for default users, as we can
+ # edit their IP white list
def ips_add(self):
_ = self.request.translate
c = self.load_default_context()
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- # NOTE(marcink): this view is allowed for default users, as we can
- # edit their IP white list
+ user_id = self.db_user_id
+ c.user = self.db_user
user_model = UserModel()
desc = self.request.POST.get('description')
@@ -506,7 +1009,8 @@ class AdminUsersView(BaseAppView, DataGr
try:
user_model.add_extra_ip(c.user.user_id, ip, desc)
audit_logger.store_web(
- 'user.edit.ip.add', action_data={'ip': ip, 'user': user_data},
+ 'user.edit.ip.add',
+ action_data={'ip': ip, 'user': user_data},
user=self._rhodecode_user)
Session().commit()
added.append(ip)
@@ -531,14 +1035,14 @@ class AdminUsersView(BaseAppView, DataGr
@CSRFRequired()
@view_config(
route_name='edit_user_ips_delete', request_method='POST')
+ # NOTE(marcink): this view is allowed for default users, as we can
+ # edit their IP white list
def ips_delete(self):
_ = self.request.translate
c = self.load_default_context()
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- # NOTE(marcink): this view is allowed for default users, as we can
- # edit their IP white list
+ user_id = self.db_user_id
+ c.user = self.db_user
ip_id = self.request.POST.get('del_ip_id')
user_model = UserModel()
@@ -563,11 +1067,9 @@ class AdminUsersView(BaseAppView, DataGr
renderer='rhodecode:templates/admin/users/user_edit.mako')
def groups_management(self):
c = self.load_default_context()
+ c.user = self.db_user
+ c.data = c.user.group_member
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- c.data = c.user.group_member
- self._redirect_for_default_user(c.user.username)
groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
for group in c.user.group_member]
c.groups = json.dumps(groups)
@@ -584,9 +1086,8 @@ class AdminUsersView(BaseAppView, DataGr
_ = self.request.translate
c = self.load_default_context()
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ user_id = self.db_user_id
+ c.user = self.db_user
user_groups = set(self.request.POST.getall('users_group_id'))
user_groups_objects = []
@@ -595,7 +1096,25 @@ class AdminUsersView(BaseAppView, DataGr
user_groups_objects.append(
UserGroupModel().get_group(safe_int(ugid)))
user_group_model = UserGroupModel()
- user_group_model.change_groups(c.user, user_groups_objects)
+ added_to_groups, removed_from_groups = \
+ user_group_model.change_groups(c.user, user_groups_objects)
+
+ user_data = c.user.get_api_data()
+ for user_group_id in added_to_groups:
+ user_group = UserGroup.get(user_group_id)
+ old_values = user_group.get_api_data()
+ audit_logger.store_web(
+ 'user_group.edit.member.add',
+ action_data={'user': user_data, 'old_data': old_values},
+ user=self._rhodecode_user)
+
+ for user_group_id in removed_from_groups:
+ user_group = UserGroup.get(user_group_id)
+ old_values = user_group.get_api_data()
+ audit_logger.store_web(
+ 'user_group.edit.member.delete',
+ action_data={'user': user_data, 'old_data': old_values},
+ user=self._rhodecode_user)
Session().commit()
c.active = 'user_groups_management'
@@ -612,10 +1131,8 @@ class AdminUsersView(BaseAppView, DataGr
def user_audit_logs(self):
_ = self.request.translate
c = self.load_default_context()
+ c.user = self.db_user
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
c.active = 'audit'
p = safe_int(self.request.GET.get('page', 1), 1)
@@ -641,10 +1158,7 @@ class AdminUsersView(BaseAppView, DataGr
def user_perms_summary(self):
_ = self.request.translate
c = self.load_default_context()
-
- user_id = self.request.matchdict.get('user_id')
- c.user = User.get_or_404(user_id)
- self._redirect_for_default_user(c.user.username)
+ c.user = self.db_user
c.active = 'perms_summary'
c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
@@ -658,11 +1172,6 @@ class AdminUsersView(BaseAppView, DataGr
renderer='json_ext')
def user_perms_summary_json(self):
self.load_default_context()
-
- user_id = self.request.matchdict.get('user_id')
- user = User.get_or_404(user_id)
- self._redirect_for_default_user(user.username)
-
- perm_user = user.AuthUser(ip_addr=self.request.remote_addr)
+ perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
return perm_user.permissions
diff --git a/rhodecode/config/environment.py b/rhodecode/config/environment.py
--- a/rhodecode/config/environment.py
+++ b/rhodecode/config/environment.py
@@ -115,7 +115,6 @@ def load_environment(global_conf, app_co
'secret': config.get('channelstream.secret')
}
- set_available_permissions(config)
db_cfg = make_db_config(clear_session=True)
repos_path = list(db_cfg.items('paths'))[0][1]
@@ -178,5 +177,6 @@ def load_pyramid_environment(global_conf
log_level=settings['vcs.server.log_level'])
utils.configure_vcs(settings)
+
if vcs_server_enabled:
connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings))
diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py
--- a/rhodecode/config/middleware.py
+++ b/rhodecode/config/middleware.py
@@ -41,6 +41,7 @@ import rhodecode
from rhodecode.model import meta
from rhodecode.config import patches
+from rhodecode.config import utils as config_utils
from rhodecode.config.routing import STATIC_FILE_PREFIX
from rhodecode.config.environment import (
load_environment, load_pyramid_environment)
@@ -56,7 +57,7 @@ from rhodecode.lib.plugins.utils import
from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
from rhodecode.subscribers import (
scan_repositories_if_enabled, write_js_routes_if_enabled,
- write_metadata_if_needed)
+ write_metadata_if_needed, inject_app_settings)
log = logging.getLogger(__name__)
@@ -146,11 +147,12 @@ def make_pyramid_app(global_config, **se
settings_pylons = settings.copy()
sanitize_settings_and_apply_defaults(settings)
+
config = Configurator(settings=settings)
+ load_pyramid_environment(global_config, settings)
+
add_pylons_compat_data(config.registry, global_config, settings_pylons)
- load_pyramid_environment(global_config, settings)
-
includeme_first(config)
includeme(config)
@@ -315,6 +317,7 @@ def includeme(config):
settings['default_locale_name'] = settings.get('lang', 'en')
# Add subscribers.
+ config.add_subscriber(inject_app_settings, ApplicationCreated)
config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
@@ -472,6 +475,9 @@ def sanitize_settings_and_apply_defaults
_sanitize_appenlight_settings(settings)
_sanitize_vcs_settings(settings)
+ # configure instance id
+ config_utils.set_instance_id(settings)
+
return settings
diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py
--- a/rhodecode/config/routing.py
+++ b/rhodecode/config/routing.py
@@ -219,37 +219,6 @@ def make_map(config):
'function': check_group},
requirements=URL_NAME_REQUIREMENTS)
- # ADMIN USER ROUTES
- with rmap.submapper(path_prefix=ADMIN_PREFIX,
- controller='admin/users') as m:
- m.connect('users', '/users',
- action='create', conditions={'method': ['POST']})
- m.connect('new_user', '/users/new',
- action='new', conditions={'method': ['GET']})
- m.connect('update_user', '/users/{user_id}',
- action='update', conditions={'method': ['PUT']})
- m.connect('delete_user', '/users/{user_id}',
- action='delete', conditions={'method': ['DELETE']})
- m.connect('edit_user', '/users/{user_id}/edit',
- action='edit', conditions={'method': ['GET']}, jsroute=True)
- m.connect('user', '/users/{user_id}',
- action='show', conditions={'method': ['GET']})
- m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
- action='reset_password', conditions={'method': ['POST']})
- m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
- action='create_personal_repo_group', conditions={'method': ['POST']})
-
- # EXTRAS USER ROUTES
- m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
- action='edit_advanced', conditions={'method': ['GET']})
- m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
- action='update_advanced', conditions={'method': ['PUT']})
-
- m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
- action='edit_global_perms', conditions={'method': ['GET']})
- m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
- action='update_global_perms', conditions={'method': ['PUT']})
-
# ADMIN SETTINGS ROUTES
with rmap.submapper(path_prefix=ADMIN_PREFIX,
controller='admin/settings') as m:
@@ -339,5 +308,4 @@ def make_map(config):
m.connect('my_account_password', '/my_account/password',
action='my_account_password', conditions={'method': ['GET']})
-
return rmap
diff --git a/rhodecode/controllers/admin/users.py b/rhodecode/controllers/admin/users.py
deleted file mode 100644
--- a/rhodecode/controllers/admin/users.py
+++ /dev/null
@@ -1,496 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2010-2017 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# 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 Affero General Public License
-# along with this program. If not, see .
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-
-"""
-Users crud controller for pylons
-"""
-
-import logging
-import formencode
-
-from formencode import htmlfill
-from pylons import request, tmpl_context as c, url, config
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
-
-from rhodecode.authentication.plugins import auth_rhodecode
-
-from rhodecode.lib import helpers as h
-from rhodecode.lib import auth
-from rhodecode.lib import audit_logger
-from rhodecode.lib.auth import (
- LoginRequired, HasPermissionAllDecorator, AuthUser)
-from rhodecode.lib.base import BaseController, render
-from rhodecode.lib.exceptions import (
- DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
- UserOwnsUserGroupsException, UserCreationError)
-from rhodecode.lib.utils2 import safe_int, AttributeDict
-
-from rhodecode.model.db import (
- PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
-from rhodecode.model.forms import (
- UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
-from rhodecode.model.repo_group import RepoGroupModel
-from rhodecode.model.user import UserModel
-from rhodecode.model.meta import Session
-from rhodecode.model.permission import PermissionModel
-
-log = logging.getLogger(__name__)
-
-
-class UsersController(BaseController):
- """REST Controller styled on the Atom Publishing Protocol"""
-
- @LoginRequired()
- def __before__(self):
- super(UsersController, self).__before__()
- c.available_permissions = config['available_permissions']
- c.allowed_languages = [
- ('en', 'English (en)'),
- ('de', 'German (de)'),
- ('fr', 'French (fr)'),
- ('it', 'Italian (it)'),
- ('ja', 'Japanese (ja)'),
- ('pl', 'Polish (pl)'),
- ('pt', 'Portuguese (pt)'),
- ('ru', 'Russian (ru)'),
- ('zh', 'Chinese (zh)'),
- ]
- PermissionModel().set_global_permission_choices(c, gettext_translator=_)
-
- def _get_personal_repo_group_template_vars(self):
- DummyUser = AttributeDict({
- 'username': '${username}',
- 'user_id': '${user_id}',
- })
- c.default_create_repo_group = RepoGroupModel() \
- .get_default_create_personal_repo_group()
- c.personal_repo_group_name = RepoGroupModel() \
- .get_personal_group_name(DummyUser)
-
- @HasPermissionAllDecorator('hg.admin')
- @auth.CSRFRequired()
- def create(self):
- c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
- user_model = UserModel()
- user_form = UserForm()()
- try:
- form_result = user_form.to_python(dict(request.POST))
- user = user_model.create(form_result)
- Session().flush()
- creation_data = user.get_api_data()
- username = form_result['username']
-
- audit_logger.store_web(
- 'user.create', action_data={'data': creation_data},
- user=c.rhodecode_user)
-
- user_link = h.link_to(h.escape(username),
- url('edit_user',
- user_id=user.user_id))
- h.flash(h.literal(_('Created user %(user_link)s')
- % {'user_link': user_link}), category='success')
- Session().commit()
- except formencode.Invalid as errors:
- self._get_personal_repo_group_template_vars()
- return htmlfill.render(
- render('admin/users/user_add.mako'),
- defaults=errors.value,
- errors=errors.error_dict or {},
- prefix_error=False,
- encoding="UTF-8",
- force_defaults=False)
- except UserCreationError as e:
- h.flash(e, 'error')
- except Exception:
- log.exception("Exception creation of user")
- h.flash(_('Error occurred during creation of user %s')
- % request.POST.get('username'), category='error')
- return redirect(h.route_path('users'))
-
- @HasPermissionAllDecorator('hg.admin')
- def new(self):
- c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
- self._get_personal_repo_group_template_vars()
- return render('admin/users/user_add.mako')
-
- @HasPermissionAllDecorator('hg.admin')
- @auth.CSRFRequired()
- def update(self, user_id):
-
- user_id = safe_int(user_id)
- c.user = User.get_or_404(user_id)
- c.active = 'profile'
- c.extern_type = c.user.extern_type
- c.extern_name = c.user.extern_name
- c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
- available_languages = [x[0] for x in c.allowed_languages]
- _form = UserForm(edit=True, available_languages=available_languages,
- old_data={'user_id': user_id,
- 'email': c.user.email})()
- form_result = {}
- old_values = c.user.get_api_data()
- try:
- form_result = _form.to_python(dict(request.POST))
- skip_attrs = ['extern_type', 'extern_name']
- # TODO: plugin should define if username can be updated
- if c.extern_type != "rhodecode":
- # forbid updating username for external accounts
- skip_attrs.append('username')
-
- UserModel().update_user(
- user_id, skip_attrs=skip_attrs, **form_result)
-
- audit_logger.store_web(
- 'user.edit', action_data={'old_data': old_values},
- user=c.rhodecode_user)
-
- Session().commit()
- h.flash(_('User updated successfully'), category='success')
- except formencode.Invalid as errors:
- defaults = errors.value
- e = errors.error_dict or {}
-
- return htmlfill.render(
- render('admin/users/user_edit.mako'),
- defaults=defaults,
- errors=e,
- prefix_error=False,
- encoding="UTF-8",
- force_defaults=False)
- except UserCreationError as e:
- h.flash(e, 'error')
- except Exception:
- log.exception("Exception updating user")
- h.flash(_('Error occurred during update of user %s')
- % form_result.get('username'), category='error')
- return redirect(url('edit_user', user_id=user_id))
-
- @HasPermissionAllDecorator('hg.admin')
- @auth.CSRFRequired()
- def delete(self, user_id):
- user_id = safe_int(user_id)
- c.user = User.get_or_404(user_id)
-
- _repos = c.user.repositories
- _repo_groups = c.user.repository_groups
- _user_groups = c.user.user_groups
-
- handle_repos = None
- handle_repo_groups = None
- handle_user_groups = None
- # dummy call for flash of handle
- set_handle_flash_repos = lambda: None
- set_handle_flash_repo_groups = lambda: None
- set_handle_flash_user_groups = lambda: None
-
- if _repos and request.POST.get('user_repos'):
- do = request.POST['user_repos']
- if do == 'detach':
- handle_repos = 'detach'
- set_handle_flash_repos = lambda: h.flash(
- _('Detached %s repositories') % len(_repos),
- category='success')
- elif do == 'delete':
- handle_repos = 'delete'
- set_handle_flash_repos = lambda: h.flash(
- _('Deleted %s repositories') % len(_repos),
- category='success')
-
- if _repo_groups and request.POST.get('user_repo_groups'):
- do = request.POST['user_repo_groups']
- if do == 'detach':
- handle_repo_groups = 'detach'
- set_handle_flash_repo_groups = lambda: h.flash(
- _('Detached %s repository groups') % len(_repo_groups),
- category='success')
- elif do == 'delete':
- handle_repo_groups = 'delete'
- set_handle_flash_repo_groups = lambda: h.flash(
- _('Deleted %s repository groups') % len(_repo_groups),
- category='success')
-
- if _user_groups and request.POST.get('user_user_groups'):
- do = request.POST['user_user_groups']
- if do == 'detach':
- handle_user_groups = 'detach'
- set_handle_flash_user_groups = lambda: h.flash(
- _('Detached %s user groups') % len(_user_groups),
- category='success')
- elif do == 'delete':
- handle_user_groups = 'delete'
- set_handle_flash_user_groups = lambda: h.flash(
- _('Deleted %s user groups') % len(_user_groups),
- category='success')
-
- old_values = c.user.get_api_data()
- try:
- UserModel().delete(c.user, handle_repos=handle_repos,
- handle_repo_groups=handle_repo_groups,
- handle_user_groups=handle_user_groups)
-
- audit_logger.store_web(
- 'user.delete', action_data={'old_data': old_values},
- user=c.rhodecode_user)
-
- Session().commit()
- set_handle_flash_repos()
- set_handle_flash_repo_groups()
- set_handle_flash_user_groups()
- h.flash(_('Successfully deleted user'), category='success')
- except (UserOwnsReposException, UserOwnsRepoGroupsException,
- UserOwnsUserGroupsException, DefaultUserException) as e:
- h.flash(e, category='warning')
- except Exception:
- log.exception("Exception during deletion of user")
- h.flash(_('An error occurred during deletion of user'),
- category='error')
- return redirect(h.route_path('users'))
-
- @HasPermissionAllDecorator('hg.admin')
- @auth.CSRFRequired()
- def reset_password(self, user_id):
- """
- toggle reset password flag for this user
- """
- user_id = safe_int(user_id)
- c.user = User.get_or_404(user_id)
- try:
- old_value = c.user.user_data.get('force_password_change')
- c.user.update_userdata(force_password_change=not old_value)
-
- if old_value:
- msg = _('Force password change disabled for user')
- audit_logger.store_web(
- 'user.edit.password_reset.disabled',
- user=c.rhodecode_user)
- else:
- msg = _('Force password change enabled for user')
- audit_logger.store_web(
- 'user.edit.password_reset.enabled',
- user=c.rhodecode_user)
-
- Session().commit()
- h.flash(msg, category='success')
- except Exception:
- log.exception("Exception during password reset for user")
- h.flash(_('An error occurred during password reset for user'),
- category='error')
-
- return redirect(url('edit_user_advanced', user_id=user_id))
-
- @HasPermissionAllDecorator('hg.admin')
- @auth.CSRFRequired()
- def create_personal_repo_group(self, user_id):
- """
- Create personal repository group for this user
- """
- from rhodecode.model.repo_group import RepoGroupModel
-
- user_id = safe_int(user_id)
- c.user = User.get_or_404(user_id)
- personal_repo_group = RepoGroup.get_user_personal_repo_group(
- c.user.user_id)
- if personal_repo_group:
- return redirect(url('edit_user_advanced', user_id=user_id))
-
- personal_repo_group_name = RepoGroupModel().get_personal_group_name(
- c.user)
- named_personal_group = RepoGroup.get_by_group_name(
- personal_repo_group_name)
- try:
-
- if named_personal_group and named_personal_group.user_id == c.user.user_id:
- # migrate the same named group, and mark it as personal
- named_personal_group.personal = True
- Session().add(named_personal_group)
- Session().commit()
- msg = _('Linked repository group `%s` as personal' % (
- personal_repo_group_name,))
- h.flash(msg, category='success')
- elif not named_personal_group:
- RepoGroupModel().create_personal_repo_group(c.user)
-
- msg = _('Created repository group `%s`' % (
- personal_repo_group_name,))
- h.flash(msg, category='success')
- else:
- msg = _('Repository group `%s` is already taken' % (
- personal_repo_group_name,))
- h.flash(msg, category='warning')
- except Exception:
- log.exception("Exception during repository group creation")
- msg = _(
- 'An error occurred during repository group creation for user')
- h.flash(msg, category='error')
- Session().rollback()
-
- return redirect(url('edit_user_advanced', user_id=user_id))
-
- @HasPermissionAllDecorator('hg.admin')
- def show(self, user_id):
- """GET /users/user_id: Show a specific item"""
- # url('user', user_id=ID)
- User.get_or_404(-1)
-
- @HasPermissionAllDecorator('hg.admin')
- def edit(self, user_id):
- """GET /users/user_id/edit: Form to edit an existing item"""
- # url('edit_user', user_id=ID)
- user_id = safe_int(user_id)
- c.user = User.get_or_404(user_id)
- if c.user.username == User.DEFAULT_USER:
- h.flash(_("You can't edit this user"), category='warning')
- return redirect(h.route_path('users'))
-
- c.active = 'profile'
- c.extern_type = c.user.extern_type
- c.extern_name = c.user.extern_name
- c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
-
- defaults = c.user.get_dict()
- defaults.update({'language': c.user.user_data.get('language')})
- return htmlfill.render(
- render('admin/users/user_edit.mako'),
- defaults=defaults,
- encoding="UTF-8",
- force_defaults=False)
-
- @HasPermissionAllDecorator('hg.admin')
- def edit_advanced(self, user_id):
- user_id = safe_int(user_id)
- user = c.user = User.get_or_404(user_id)
- if user.username == User.DEFAULT_USER:
- h.flash(_("You can't edit this user"), category='warning')
- return redirect(h.route_path('users'))
-
- c.active = 'advanced'
- c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
- c.personal_repo_group_name = RepoGroupModel()\
- .get_personal_group_name(user)
-
- c.user_to_review_rules = sorted(
- (x.user for x in c.user.user_review_rules),
- key=lambda u: u.username.lower())
-
- c.first_admin = User.get_first_super_admin()
- defaults = user.get_dict()
-
- # Interim workaround if the user participated on any pull requests as a
- # reviewer.
- has_review = len(user.reviewer_pull_requests)
- c.can_delete_user = not has_review
- c.can_delete_user_message = ''
- inactive_link = h.link_to(
- 'inactive', h.url('edit_user', user_id=user_id, anchor='active'))
- if has_review == 1:
- c.can_delete_user_message = h.literal(_(
- 'The user participates as reviewer in {} pull request and '
- 'cannot be deleted. \nYou can set the user to '
- '"{}" instead of deleting it.').format(
- has_review, inactive_link))
- elif has_review:
- c.can_delete_user_message = h.literal(_(
- 'The user participates as reviewer in {} pull requests and '
- 'cannot be deleted. \nYou can set the user to '
- '"{}" instead of deleting it.').format(
- has_review, inactive_link))
-
- return htmlfill.render(
- render('admin/users/user_edit.mako'),
- defaults=defaults,
- encoding="UTF-8",
- force_defaults=False)
-
- @HasPermissionAllDecorator('hg.admin')
- def edit_global_perms(self, user_id):
- user_id = safe_int(user_id)
- c.user = User.get_or_404(user_id)
- if c.user.username == User.DEFAULT_USER:
- h.flash(_("You can't edit this user"), category='warning')
- return redirect(h.route_path('users'))
-
- c.active = 'global_perms'
-
- c.default_user = User.get_default_user()
- defaults = c.user.get_dict()
- defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
- defaults.update(c.default_user.get_default_perms())
- defaults.update(c.user.get_default_perms())
-
- return htmlfill.render(
- render('admin/users/user_edit.mako'),
- defaults=defaults,
- encoding="UTF-8",
- force_defaults=False)
-
- @HasPermissionAllDecorator('hg.admin')
- @auth.CSRFRequired()
- def update_global_perms(self, user_id):
- user_id = safe_int(user_id)
- user = User.get_or_404(user_id)
- c.active = 'global_perms'
- try:
- # first stage that verifies the checkbox
- _form = UserIndividualPermissionsForm()
- form_result = _form.to_python(dict(request.POST))
- inherit_perms = form_result['inherit_default_permissions']
- user.inherit_default_permissions = inherit_perms
- Session().add(user)
-
- if not inherit_perms:
- # only update the individual ones if we un check the flag
- _form = UserPermissionsForm(
- [x[0] for x in c.repo_create_choices],
- [x[0] for x in c.repo_create_on_write_choices],
- [x[0] for x in c.repo_group_create_choices],
- [x[0] for x in c.user_group_create_choices],
- [x[0] for x in c.fork_choices],
- [x[0] for x in c.inherit_default_permission_choices])()
-
- form_result = _form.to_python(dict(request.POST))
- form_result.update({'perm_user_id': user.user_id})
-
- PermissionModel().update_user_permissions(form_result)
-
- # TODO(marcink): implement global permissions
- # audit_log.store_web('user.edit.permissions')
-
- Session().commit()
- h.flash(_('User global permissions updated successfully'),
- category='success')
-
- except formencode.Invalid as errors:
- defaults = errors.value
- c.user = user
- return htmlfill.render(
- render('admin/users/user_edit.mako'),
- defaults=defaults,
- errors=errors.error_dict or {},
- prefix_error=False,
- encoding="UTF-8",
- force_defaults=False)
- except Exception:
- log.exception("Exception during permissions saving")
- h.flash(_('An error occurred during permissions saving'),
- category='error')
- return redirect(url('edit_user_global_perms', user_id=user_id))
-
-
diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py
--- a/rhodecode/lib/auth.py
+++ b/rhodecode/lib/auth.py
@@ -1012,13 +1012,16 @@ class AuthUser(object):
log.debug('No data in %s that could been used to log in', self)
if not is_user_loaded:
- log.debug('Failed to load user. Fallback to default user')
+ log.debug(
+ 'Failed to load user. Fallback to default user %s', anon_user)
# if we cannot authenticate user try anonymous
if anon_user.active:
+ log.debug('default user is active, using it as a session user')
user_model.fill_data(self, user_id=anon_user.user_id)
# then we set this user is logged in
self.is_authenticated = True
else:
+ log.debug('default user is NOT active')
# in case of disabled anonymous user we reset some of the
# parameters so such user is "corrupted", skipping the fill_data
for attr in ['user_id', 'username', 'admin', 'active']:
@@ -1277,25 +1280,26 @@ class AuthUser(object):
return _set or set(['0.0.0.0/0', '::/0'])
-def set_available_permissions(config):
+def set_available_permissions(settings):
"""
- This function will propagate pylons globals with all available defined
+ This function will propagate pyramid settings with all available defined
permission given in db. We don't want to check each time from db for new
permissions since adding a new permission also requires application restart
ie. to decorate new views with the newly created permission
- :param config: current pylons config instance
+ :param settings: current pyramid registry.settings
"""
- log.info('getting information about all available permissions')
+ log.debug('auth: getting information about all available permissions')
try:
sa = meta.Session
all_perms = sa.query(Permission).all()
- config['available_permissions'] = [x.permission_name for x in all_perms]
+ settings.setdefault('available_permissions',
+ [x.permission_name for x in all_perms])
+ log.debug('auth: set available permissions')
except Exception:
- log.error(traceback.format_exc())
- finally:
- meta.Session.remove()
+ log.exception('Failed to fetch permissions from the database.')
+ raise
def get_csrf_token(session, force_new=False, save_if_missing=True):
diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py
--- a/rhodecode/model/db.py
+++ b/rhodecode/model/db.py
@@ -681,6 +681,17 @@ class User(Base, BaseModel):
return 'NO_FEED_TOKEN_AVAILABLE'
@classmethod
+ def get(cls, user_id, cache=False):
+ if not user_id:
+ return
+
+ user = cls.query()
+ if cache:
+ user = user.options(
+ FromCache("sql_cache_short", "get_users_%s" % user_id))
+ return user.get(user_id)
+
+ @classmethod
def extra_valid_auth_tokens(cls, user, role=None):
tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
.filter(or_(UserApiKeys.expires == -1,
@@ -1341,6 +1352,9 @@ class UserGroup(Base, BaseModel):
@classmethod
def get(cls, user_group_id, cache=False):
+ if not user_group_id:
+ return
+
user_group = cls.query()
if cache:
user_group = user_group.options(
diff --git a/rhodecode/model/user_group.py b/rhodecode/model/user_group.py
--- a/rhodecode/model/user_group.py
+++ b/rhodecode/model/user_group.py
@@ -554,16 +554,22 @@ class UserGroupModel(BaseModel):
groups_to_remove = current_groups - groups
groups_to_add = groups - current_groups
+ removed_from_groups = []
+ added_to_groups = []
for gr in groups_to_remove:
log.debug('Removing user %s from user group %s',
user.username, gr.users_group_name)
+ removed_from_groups.append(gr.users_group_id)
self.remove_user_from_group(gr.users_group_name, user.username)
for gr in groups_to_add:
log.debug('Adding user %s to user group %s',
user.username, gr.users_group_name)
+ added_to_groups.append(gr.users_group_id)
UserGroupModel().add_user_to_group(
gr.users_group_name, user.username)
+ return added_to_groups, removed_from_groups
+
def _serialize_user_group(self, user_group):
import rhodecode.lib.helpers as h
return {
diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js
--- a/rhodecode/public/js/rhodecode/routes.js
+++ b/rhodecode/public/js/rhodecode/routes.js
@@ -12,7 +12,6 @@
******************************************************************************/
function registerRCRoutes() {
// routes registration
- pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
pyroutes.register('favicon', '/favicon.ico', []);
pyroutes.register('robots', '/robots.txt', []);
pyroutes.register('auth_home', '/_admin/auth*traverse', []);
@@ -66,6 +65,16 @@ function registerRCRoutes() {
pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
pyroutes.register('users', '/_admin/users', []);
pyroutes.register('users_data', '/_admin/users_data', []);
+ pyroutes.register('users_create', '/_admin/users/create', []);
+ pyroutes.register('users_new', '/_admin/users/new', []);
+ pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
+ pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
+ pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
+ pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
+ pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
+ pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
+ pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
+ pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
diff --git a/rhodecode/subscribers.py b/rhodecode/subscribers.py
--- a/rhodecode/subscribers.py
+++ b/rhodecode/subscribers.py
@@ -35,6 +35,7 @@ from threading import Thread
from rhodecode.translation import _ as tsf
from rhodecode.config.jsroutes import generate_jsroutes_content
+from rhodecode.lib import auth
import rhodecode
@@ -140,6 +141,12 @@ def add_pylons_context(event):
pylons.tmpl_context._push_object(context)
+def inject_app_settings(event):
+ settings = event.app.registry.settings
+ # inject info about available permissions
+ auth.set_available_permissions(settings)
+
+
def scan_repositories_if_enabled(event):
"""
This is subscribed to the `pyramid.events.ApplicationCreated` event. It
diff --git a/rhodecode/templates/admin/my_account/my_account_auth_tokens.mako b/rhodecode/templates/admin/my_account/my_account_auth_tokens.mako
--- a/rhodecode/templates/admin/my_account/my_account_auth_tokens.mako
+++ b/rhodecode/templates/admin/my_account/my_account_auth_tokens.mako
@@ -102,7 +102,6 @@ var select2Options = {
};
$("#role").select2(select2Options);
-
var preloadData = {
results: [
% for entry in c.lifetime_values:
diff --git a/rhodecode/templates/admin/user_groups/user_group_edit_settings.mako b/rhodecode/templates/admin/user_groups/user_group_edit_settings.mako
--- a/rhodecode/templates/admin/user_groups/user_group_edit_settings.mako
+++ b/rhodecode/templates/admin/user_groups/user_group_edit_settings.mako
@@ -77,7 +77,7 @@
${base.gravatar(user.email, 16)}
- ${h.link_to(h.person(user), h.url( 'edit_user',user_id=user.user_id))}
+ ${h.link_to(h.person(user), h.route_path('user_edit',user_id=user.user_id))}
@@ -134,7 +134,7 @@
function addMember(user, fromUserGroup) {
var gravatar = user.icon_link;
var username = user.value_display;
- var userLink = pyroutes.url('edit_user', {"user_id": user.id});
+ var userLink = pyroutes.url('user_edit', {"user_id": user.id});
var uid = user.id;
if (fromUserGroup) {
diff --git a/rhodecode/templates/admin/users/user_add.mako b/rhodecode/templates/admin/users/user_add.mako
--- a/rhodecode/templates/admin/users/user_add.mako
+++ b/rhodecode/templates/admin/users/user_add.mako
@@ -26,7 +26,7 @@
${self.breadcrumbs()}
- ${h.secure_form(h.url('users'))}
+ ${h.secure_form(h.route_path('users_create'), request=request)}
|