diff --git a/rhodecode/model/user.py b/rhodecode/model/user.py --- a/rhodecode/model/user.py +++ b/rhodecode/model/user.py @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.user - ~~~~~~~~~~~~~~~~~~~~ - - users model for RhodeCode - - :created_on: Apr 9, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -22,24 +11,32 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.user +~~~~~~~~~~~~~~~~~~~~ + +users model for RhodeCode + +:created_on: Apr 9, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import traceback -import itertools -import collections from pylons import url from pylons.i18n.translation import _ from sqlalchemy.exc import DatabaseError -from sqlalchemy.orm import joinedload + from rhodecode.lib.utils2 import safe_unicode, generate_api_key, get_current_rhodecode_user from rhodecode.lib.caching_query import FromCache from rhodecode.model import BaseModel -from rhodecode.model.db import User, Repository, Permission, \ - UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \ - Notification, RepoGroup, UserGroupRepoGroupToPerm, \ - UserEmailMap, UserIpMap, UserGroupUserGroupToPerm, UserGroup +from rhodecode.model.db import User, UserToPerm, Notification, \ + UserEmailMap, UserIpMap from rhodecode.lib.exceptions import DefaultUserException, \ UserOwnsReposException from rhodecode.model.meta import Session @@ -47,8 +44,6 @@ from rhodecode.model.meta import Session log = logging.getLogger(__name__) -PERM_WEIGHTS = Permission.PERM_WEIGHTS - class UserModel(BaseModel): cls = User @@ -68,7 +63,8 @@ class UserModel(BaseModel): if case_insensitive: user = self.sa.query(User).filter(User.username.ilike(username)) else: - user = self.sa.query(User).filter(User.username == username) + user = self.sa.query(User)\ + .filter(User.username == username) if cache: user = user.options(FromCache("sql_cache_short", "get_user_%s" % username)) @@ -93,7 +89,6 @@ class UserModel(BaseModel): } # raises UserCreationError if it's not allowed check_allowed_create_user(user_data, cur_user) - from rhodecode.lib.auth import get_crypt_password try: new_user = User() @@ -114,8 +109,8 @@ class UserModel(BaseModel): raise def create_or_update(self, username, password, email, firstname='', - lastname='', active=True, admin=False, ldap_dn=None, - cur_user=None): + lastname='', active=True, admin=False, + extern_type=None, extern_name=None, cur_user=None): """ Creates a new instance if not found, or updates current one @@ -127,13 +122,14 @@ class UserModel(BaseModel): :param lastname: :param active: :param admin: - :param ldap_dn: + :param extern_name: + :param extern_type: :param cur_user: """ if not cur_user: cur_user = getattr(get_current_rhodecode_user(), 'username', None) - from rhodecode.lib.auth import get_crypt_password + from rhodecode.lib.auth import get_crypt_password, check_password from rhodecode.lib.hooks import log_create_user, check_allowed_create_user user_data = { 'username': username, 'password': password, @@ -157,15 +153,24 @@ class UserModel(BaseModel): try: new_user.username = username new_user.admin = admin - # set password only if creating an user or password is changed - if not edit or user.password != password: - new_user.password = get_crypt_password(password) if password else None - new_user.api_key = generate_api_key(username) new_user.email = email new_user.active = active - new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None + new_user.extern_name = safe_unicode(extern_name) if extern_name else None + new_user.extern_type = safe_unicode(extern_type) if extern_type else None new_user.name = firstname new_user.lastname = lastname + + if not edit: + new_user.api_key = generate_api_key(username) + + # set password only if creating an user or password is changed + password_change = new_user.password and not check_password(password, + new_user.password) + if not edit or password_change: + reason = 'new password' if edit else 'new user' + log.debug('Updating password reason=>%s' % (reason,)) + new_user.password = get_crypt_password(password) if password else None + self.sa.add(new_user) if not edit: @@ -175,116 +180,13 @@ class UserModel(BaseModel): log.error(traceback.format_exc()) raise - def create_for_container_auth(self, username, attrs, cur_user=None): - """ - Creates the given user if it's not already in the database - - :param username: - :param attrs: - :param cur_user: - """ - if not cur_user: - cur_user = getattr(get_current_rhodecode_user(), 'username', None) - if self.get_by_username(username, case_insensitive=True) is None: - # autogenerate email for container account without one - generate_email = lambda usr: '%s@container_auth.account' % usr - firstname = attrs['name'] - lastname = attrs['lastname'] - active = attrs.get('active', True) - email = attrs['email'] or generate_email(username) - - from rhodecode.lib.hooks import log_create_user, check_allowed_create_user - user_data = { - 'username': username, 'password': None, - 'email': email, 'firstname': firstname, 'lastname': lastname, - 'active': attrs.get('active', True), 'admin': False - } - # raises UserCreationError if it's not allowed - check_allowed_create_user(user_data, cur_user) - - try: - new_user = User() - new_user.username = username - new_user.password = None - new_user.api_key = generate_api_key(username) - new_user.email = email - new_user.active = active - new_user.name = firstname - new_user.lastname = lastname - - self.sa.add(new_user) - log_create_user(new_user.get_dict(), cur_user) - return new_user - except (DatabaseError,): - log.error(traceback.format_exc()) - self.sa.rollback() - raise - log.debug('User %s already exists. Skipping creation of account' - ' for container auth.', username) - return None - - def create_ldap(self, username, password, user_dn, attrs, cur_user=None): - """ - Checks if user is in database, if not creates this user marked - as ldap user - - :param username: - :param password: - :param user_dn: - :param attrs: - :param cur_user: - """ - if not cur_user: - cur_user = getattr(get_current_rhodecode_user(), 'username', None) - from rhodecode.lib.auth import get_crypt_password - log.debug('Checking for such ldap account in RhodeCode database') - if self.get_by_username(username, case_insensitive=True) is None: - # autogenerate email for container account without one - generate_email = lambda usr: '%s@ldap.account' % usr - password = get_crypt_password(password) - firstname = attrs['name'] - lastname = attrs['lastname'] - active = attrs.get('active', True) - email = attrs['email'] or generate_email(username) - - from rhodecode.lib.hooks import log_create_user, check_allowed_create_user - user_data = { - 'username': username, 'password': password, - 'email': email, 'firstname': firstname, 'lastname': lastname, - 'active': attrs.get('active', True), 'admin': False - } - # raises UserCreationError if it's not allowed - check_allowed_create_user(user_data, cur_user) - - try: - new_user = User() - username = username.lower() - # add ldap account always lowercase - new_user.username = username - new_user.password = password - new_user.api_key = generate_api_key(username) - new_user.email = email - new_user.active = active - new_user.ldap_dn = safe_unicode(user_dn) - new_user.name = firstname - new_user.lastname = lastname - self.sa.add(new_user) - - log_create_user(new_user.get_dict(), cur_user) - return new_user - except (DatabaseError,): - log.error(traceback.format_exc()) - self.sa.rollback() - raise - log.debug('this %s user exists skipping creation of ldap account', - username) - return None - def create_registration(self, form_data): from rhodecode.model.notification import NotificationModel try: form_data['admin'] = False + form_data['extern_name'] = 'rhodecode' + form_data['extern_type'] = 'rhodecode' new_user = self.create(form_data) self.sa.add(new_user) @@ -313,18 +215,19 @@ class UserModel(BaseModel): from rhodecode.lib.auth import get_crypt_password try: user = self.get(user_id, cache=False) - if user.username == 'default': + if user.username == User.DEFAULT_USER: raise DefaultUserException( - _("You can't Edit this user since it's" - " crucial for entire application")) + _("You can't Edit this user since it's " + "crucial for entire application")) for k, v in form_data.items(): if k in skip_attrs: continue if k == 'new_password' and v: user.password = get_crypt_password(v) - user.api_key = generate_api_key(user.username) else: + # old legacy thing orm models store firstname as name, + # need proper refactor to username if k == 'firstname': k = 'name' setattr(user, k, v) @@ -337,7 +240,7 @@ class UserModel(BaseModel): from rhodecode.lib.auth import get_crypt_password try: user = self._get_user(user) - if user.username == 'default': + if user.username == User.DEFAULT_USER: raise DefaultUserException( _("You can't Edit this user since it's" " crucial for entire application") @@ -346,7 +249,6 @@ class UserModel(BaseModel): for k, v in kwargs.items(): if k == 'password' and v: v = get_crypt_password(v) - user.api_key = generate_api_key(user.username) setattr(user, k, v) self.sa.add(user) @@ -361,7 +263,7 @@ class UserModel(BaseModel): user = self._get_user(user) try: - if user.username == 'default': + if user.username == User.DEFAULT_USER: raise DefaultUserException( _(u"You can't remove this user since it's" " crucial for entire application") @@ -418,7 +320,6 @@ class UserModel(BaseModel): auth.PasswordGenerator.ALPHABETS_BIG_SMALL) if user: user.password = auth.get_crypt_password(new_passwd) - user.api_key = auth.generate_api_key(user.username) Session().add(user) Session().commit() log.info('change password for %s' % user_email) @@ -441,7 +342,7 @@ class UserModel(BaseModel): return True - def fill_data(self, auth_user, user_id=None, api_key=None): + def fill_data(self, auth_user, user_id=None, api_key=None, username=None): """ Fetches auth_user by user_id,or api_key if present. Fills auth_user attributes with those taken from database. @@ -451,20 +352,25 @@ class UserModel(BaseModel): :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 + :param username: username to fetch by """ - if user_id is None and api_key is None: - raise Exception('You need to pass user_id or api_key') + if user_id is None and api_key is None and username is None: + raise Exception('You need to pass user_id, api_key or username') try: - if api_key: + dbuser = None + if user_id: + dbuser = self.get(user_id) + elif api_key: dbuser = self.get_by_api_key(api_key) - else: - dbuser = self.get(user_id) + elif username: + dbuser = self.get_by_username(username) 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) + for k, v in dbuser.get_dict().iteritems(): + if k not in ['api_keys', 'permissions']: + setattr(auth_user, k, v) else: return False @@ -475,296 +381,6 @@ class UserModel(BaseModel): return True - def fill_perms(self, user, explicit=True, algo='higherwin'): - """ - 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 - :param explicit: In case there are permissions both for user and a group - that user is part of, explicit flag will defiine if user will - explicitly override permissions from group, if it's False it will - make decision based on the algo - :param algo: algorithm to decide what permission should be choose if - it's multiple defined, eg user in two different groups. It also - decides if explicit flag is turned off how to specify the permission - for case when user is in a group + have defined separate permission - """ - RK = 'repositories' - GK = 'repositories_groups' - UK = 'user_groups' - GLOBAL = 'global' - user.permissions[RK] = {} - user.permissions[GK] = {} - user.permissions[UK] = {} - user.permissions[GLOBAL] = set() - - def _choose_perm(new_perm, cur_perm): - new_perm_val = PERM_WEIGHTS[new_perm] - cur_perm_val = PERM_WEIGHTS[cur_perm] - if algo == 'higherwin': - if new_perm_val > cur_perm_val: - return new_perm - return cur_perm - elif algo == 'lowerwin': - if new_perm_val < cur_perm_val: - return new_perm - return cur_perm - - #====================================================================== - # 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) - default_user_group_perms = Permission.get_default_user_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 - - # repository groups - for perm in default_repo_groups_perms: - rg_k = perm.UserRepoGroupToPerm.group.group_name - p = 'group.admin' - user.permissions[GK][rg_k] = p - - # user groups - for perm in default_user_group_perms: - u_k = perm.UserUserGroupToPerm.user_group.users_group_name - p = 'usergroup.admin' - user.permissions[UK][u_k] = p - return user - - #================================================================== - # SET DEFAULTS GLOBAL, REPOS, REPOSITORY 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 - for perm in default_repo_perms: - r_k = perm.UserRepoToPerm.repository.repo_name - 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 = 'repository.admin' - else: - p = perm.Permission.permission_name - - user.permissions[RK][r_k] = p - - # defaults for repository groups taken from default user permission - # on given group - for perm in default_repo_groups_perms: - rg_k = perm.UserRepoGroupToPerm.group.group_name - p = perm.Permission.permission_name - user.permissions[GK][rg_k] = p - - # defaults for user groups taken from default user permission - # on given user group - for perm in default_user_group_perms: - u_k = perm.UserUserGroupToPerm.user_group.users_group_name - p = perm.Permission.permission_name - user.permissions[UK][u_k] = p - - #====================================================================== - # !! 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', - 'hg.usergroup.create.false', 'hg.usergroup.create.true' - ]) - - # USER GROUPS comes first - # user group global permissions - user_perms_from_users_groups = self.sa.query(UserGroupToPerm)\ - .options(joinedload(UserGroupToPerm.permission))\ - .join((UserGroupMember, UserGroupToPerm.users_group_id == - UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == uid)\ - .order_by(UserGroupToPerm.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.permissions[GLOBAL].add(perm.permission.permission_name) - - # 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: - # NEED TO IGNORE all configurable permissions and - # replace them with explicitly set - user.permissions[GLOBAL] = user.permissions[GLOBAL]\ - .difference(_configurable) - - for perm in user_perms: - user.permissions[GLOBAL].add(perm.permission.permission_name) - ## END GLOBAL PERMISSIONS - - #====================================================================== - # !! PERMISSIONS FOR REPOSITORIES !! - #====================================================================== - #====================================================================== - # check if user is part of user groups for this repository and - # fill in his permission from it. _choose_perm decides of which - # permission should be selected based on selected method - #====================================================================== - - # user group for repositories permissions - user_repo_perms_from_users_groups = \ - self.sa.query(UserGroupRepoToPerm, Permission, Repository,)\ - .join((Repository, UserGroupRepoToPerm.repository_id == - Repository.repo_id))\ - .join((Permission, UserGroupRepoToPerm.permission_id == - Permission.permission_id))\ - .join((UserGroupMember, UserGroupRepoToPerm.users_group_id == - UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == uid)\ - .all() - - multiple_counter = collections.defaultdict(int) - for perm in user_repo_perms_from_users_groups: - r_k = perm.UserGroupRepoToPerm.repository.repo_name - multiple_counter[r_k] += 1 - p = perm.Permission.permission_name - cur_perm = user.permissions[RK][r_k] - - if perm.Repository.user_id == uid: - # set admin if owner - p = 'repository.admin' - else: - if multiple_counter[r_k] > 1: - p = _choose_perm(p, cur_perm) - user.permissions[RK][r_k] = p - - # user explicit permissions for repositories, overrides any specified - # by the group permission - user_repo_perms = Permission.get_default_perms(uid) - for perm in user_repo_perms: - r_k = perm.UserRepoToPerm.repository.repo_name - cur_perm = user.permissions[RK][r_k] - # set admin if owner - if perm.Repository.user_id == uid: - p = 'repository.admin' - else: - p = perm.Permission.permission_name - if not explicit: - p = _choose_perm(p, cur_perm) - user.permissions[RK][r_k] = p - - #====================================================================== - # !! PERMISSIONS FOR REPOSITORY GROUPS !! - #====================================================================== - #====================================================================== - # check if user is part of user groups for this repository groups and - # fill in his permission from it. _choose_perm decides of which - # permission should be selected based on selected method - #====================================================================== - # user group for repo groups permissions - user_repo_group_perms_from_users_groups = \ - self.sa.query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\ - .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((Permission, UserGroupRepoGroupToPerm.permission_id - == Permission.permission_id))\ - .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id - == UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == uid)\ - .all() - - multiple_counter = collections.defaultdict(int) - for perm in user_repo_group_perms_from_users_groups: - g_k = perm.UserGroupRepoGroupToPerm.group.group_name - multiple_counter[g_k] += 1 - p = perm.Permission.permission_name - cur_perm = user.permissions[GK][g_k] - if multiple_counter[g_k] > 1: - p = _choose_perm(p, cur_perm) - user.permissions[GK][g_k] = p - - # user explicit permissions for repository groups - user_repo_groups_perms = Permission.get_default_group_perms(uid) - for perm in user_repo_groups_perms: - rg_k = perm.UserRepoGroupToPerm.group.group_name - p = perm.Permission.permission_name - cur_perm = user.permissions[GK][rg_k] - if not explicit: - p = _choose_perm(p, cur_perm) - user.permissions[GK][rg_k] = p - - #====================================================================== - # !! PERMISSIONS FOR USER GROUPS !! - #====================================================================== - # user group for user group permissions - user_group_user_groups_perms = \ - self.sa.query(UserGroupUserGroupToPerm, Permission, UserGroup)\ - .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id - == UserGroup.users_group_id))\ - .join((Permission, UserGroupUserGroupToPerm.permission_id - == Permission.permission_id))\ - .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id - == UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == uid)\ - .all() - - multiple_counter = collections.defaultdict(int) - for perm in user_group_user_groups_perms: - g_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name - multiple_counter[g_k] += 1 - p = perm.Permission.permission_name - cur_perm = user.permissions[UK][g_k] - if multiple_counter[g_k] > 1: - p = _choose_perm(p, cur_perm) - user.permissions[UK][g_k] = p - - #user explicit permission for user groups - user_user_groups_perms = Permission.get_default_user_group_perms(uid) - for perm in user_user_groups_perms: - u_k = perm.UserUserGroupToPerm.user_group.users_group_name - p = perm.Permission.permission_name - cur_perm = user.permissions[UK][u_k] - if not explicit: - p = _choose_perm(p, cur_perm) - user.permissions[UK][u_k] = p - - return user - def has_perm(self, user, perm): perm = self._get_perm(perm) user = self._get_user(user) @@ -792,6 +408,7 @@ class UserModel(BaseModel): new.user = user new.permission = perm self.sa.add(new) + return new def revoke_perm(self, user, perm): """