Changeset - 001c7e2ae986
[Not reviewed]
beta
0 3 0
Marcin Kuzminski - 13 years ago 2012-07-25 00:38:05
marcin@python-works.com
fixed api issue with changing username during update_user
3 files changed with 39 insertions and 16 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/api/api.py
Show inline comments
 
@@ -167,210 +167,211 @@ class ApiController(JSONRPCController):
 
        Get a user by username
 

	
 
        :param apiuser:
 
        :param userid:
 
        """
 

	
 
        user = get_user_or_error(userid)
 
        data = user.get_api_data()
 
        data['permissions'] = AuthUser(user_id=user.user_id).permissions
 
        return data
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def get_users(self, apiuser):
 
        """"
 
        Get all users
 

	
 
        :param apiuser:
 
        """
 

	
 
        result = []
 
        for user in UserModel().get_all():
 
            result.append(user.get_api_data())
 
        return result
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def create_user(self, apiuser, username, email, password,
 
                    firstname=Optional(None), lastname=Optional(None),
 
                    active=Optional(True), admin=Optional(False),
 
                    ldap_dn=Optional(None)):
 
        """
 
        Create new user
 

	
 
        :param apiuser:
 
        :param username:
 
        :param email:
 
        :param password:
 
        :param firstname:
 
        :param lastname:
 
        :param active:
 
        :param admin:
 
        :param ldap_dn:
 
        """
 

	
 
        if UserModel().get_by_username(username):
 
            raise JSONRPCError("user `%s` already exist" % username)
 

	
 
        if UserModel().get_by_email(email, case_insensitive=True):
 
            raise JSONRPCError("email `%s` already exist" % email)
 

	
 
        if ldap_dn:
 
            # generate temporary password if ldap_dn
 
            password = PasswordGenerator().gen_password(length=8)
 

	
 
        try:
 
            user = UserModel().create_or_update(
 
                username=Optional.extract(username),
 
                password=Optional.extract(password),
 
                email=Optional.extract(email),
 
                firstname=Optional.extract(firstname),
 
                lastname=Optional.extract(lastname),
 
                active=Optional.extract(active),
 
                admin=Optional.extract(admin),
 
                ldap_dn=Optional.extract(ldap_dn)
 
            )
 
            Session().commit()
 
            return dict(
 
                msg='created new user `%s`' % username,
 
                user=user.get_api_data()
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to create user `%s`' % username)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def update_user(self, apiuser, userid, username=Optional(None),
 
                    email=Optional(None), firstname=Optional(None),
 
                    lastname=Optional(None), active=Optional(None),
 
                    admin=Optional(None), ldap_dn=Optional(None),
 
                    password=Optional(None)):
 
        """
 
        Updates given user
 

	
 
        :param apiuser:
 
        :param userid:
 
        :param username:
 
        :param email:
 
        :param firstname:
 
        :param lastname:
 
        :param active:
 
        :param admin:
 
        :param ldap_dn:
 
        :param password:
 
        """
 

	
 
        user = get_user_or_error(userid)
 

	
 
        #return old attribute if Optional is passed. We don't change parameter
 
        # so user doesn't get updated parameters
 
        get = lambda attr, name: (
 
                getattr(user, name) if isinstance(attr, Optional) else attr
 
        )
 
        # call function and store only updated arguments
 
        updates = {}
 

	
 
        def store_update(attr, name):
 
            if not isinstance(attr, Optional):
 
                updates[name] = attr
 

	
 
        try:
 

	
 
            user = UserModel().create_or_update(
 
                username=get(username, 'username'),
 
                password=get(password, 'password'),
 
                email=get(email, 'email'),
 
                firstname=get(firstname, 'name'),
 
                lastname=get(lastname, 'lastname'),
 
                active=get(active, 'active'),
 
                admin=get(admin, 'admin'),
 
                ldap_dn=get(ldap_dn, 'ldap_dn')
 
            )
 
            store_update(username, 'username')
 
            store_update(password, 'password')
 
            store_update(email, 'email')
 
            store_update(firstname, 'name')
 
            store_update(lastname, 'lastname')
 
            store_update(active, 'active')
 
            store_update(admin, 'admin')
 
            store_update(ldap_dn, 'ldap_dn')
 

	
 
            user = UserModel().update_user(user, **updates)
 
            Session().commit()
 
            return dict(
 
                msg='updated user ID:%s %s' % (user.user_id, user.username),
 
                user=user.get_api_data()
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to update user `%s`' % userid)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def delete_user(self, apiuser, userid):
 
        """"
 
        Deletes an user
 

	
 
        :param apiuser:
 
        :param userid:
 
        """
 
        user = get_user_or_error(userid)
 

	
 
        try:
 
            UserModel().delete(userid)
 
            Session().commit()
 
            return dict(
 
                msg='deleted user ID:%s %s' % (user.user_id, user.username),
 
                user=None
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id,
 
                                                              user.username))
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def get_users_group(self, apiuser, usersgroupid):
 
        """"
 
        Get users group by name or id
 

	
 
        :param apiuser:
 
        :param usersgroupid:
 
        """
 
        users_group = get_users_group_or_error(usersgroupid)
 

	
 
        data = users_group.get_api_data()
 

	
 
        members = []
 
        for user in users_group.members:
 
            user = user.user
 
            members.append(user.get_api_data())
 
        data['members'] = members
 
        return data
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def get_users_groups(self, apiuser):
 
        """"
 
        Get all users groups
 

	
 
        :param apiuser:
 
        """
 

	
 
        result = []
 
        for users_group in UsersGroupModel().get_all():
 
            result.append(users_group.get_api_data())
 
        return result
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def create_users_group(self, apiuser, group_name, active=Optional(True)):
 
        """
 
        Creates an new usergroup
 

	
 
        :param apiuser:
 
        :param group_name:
 
        :param active:
 
        """
 

	
 
        if UsersGroupModel().get_by_name(group_name):
 
            raise JSONRPCError("users group `%s` already exist" % group_name)
 

	
 
        try:
 
            active = Optional.extract(active)
 
            ug = UsersGroupModel().create(name=group_name, active=active)
 
            Session().commit()
 
            return dict(
 
                msg='created new users group `%s`' % group_name,
 
                users_group=ug.get_api_data()
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to create group `%s`' % group_name)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def add_user_to_users_group(self, apiuser, usersgroupid, userid):
 
        """"
 
        Add a user to a users group
 

	
 
        :param apiuser:
 
        :param usersgroupid:
 
        :param userid:
rhodecode/model/user.py
Show inline comments
 
@@ -185,192 +185,214 @@ class UserModel(BaseModel):
 
        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):
 
        """
 
        Checks if user is in database, if not creates this user marked
 
        as ldap user
 

	
 
        :param username:
 
        :param password:
 
        :param user_dn:
 
        :param attrs:
 
        """
 
        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 ldap account without one
 
            generate_email = lambda usr: '%s@ldap.account' % usr
 

	
 
            try:
 
                new_user = User()
 
                username = username.lower()
 
                # add ldap account always lowercase
 
                new_user.username = username
 
                new_user.password = get_crypt_password(password)
 
                new_user.api_key = generate_api_key(username)
 
                new_user.email = attrs['email'] or generate_email(username)
 
                new_user.active = attrs.get('active', True)
 
                new_user.ldap_dn = safe_unicode(user_dn)
 
                new_user.name = attrs['name']
 
                new_user.lastname = attrs['lastname']
 

	
 
                self.sa.add(new_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
 
            new_user = self.create(form_data)
 

	
 
            self.sa.add(new_user)
 
            self.sa.flush()
 

	
 
            # notification to admins
 
            subject = _('new user registration')
 
            body = ('New user registration\n'
 
                    '---------------------\n'
 
                    '- Username: %s\n'
 
                    '- Full Name: %s\n'
 
                    '- Email: %s\n')
 
            body = body % (new_user.username, new_user.full_name,
 
                           new_user.email)
 
            edit_url = url('edit_user', id=new_user.user_id, qualified=True)
 
            kw = {'registered_user_url': edit_url}
 
            NotificationModel().create(created_by=new_user, subject=subject,
 
                                       body=body, recipients=None,
 
                                       type_=Notification.TYPE_REGISTRATION,
 
                                       email_kwargs=kw)
 

	
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def update(self, user_id, form_data):
 
        from rhodecode.lib.auth import get_crypt_password
 
        try:
 
            user = self.get(user_id, cache=False)
 
            if user.username == 'default':
 
                raise DefaultUserException(
 
                                _("You can't Edit this user since it's"
 
                                  " crucial for entire application"))
 

	
 
            for k, v in form_data.items():
 
                if k == 'new_password' and v:
 
                    user.password = get_crypt_password(v)
 
                    user.api_key = generate_api_key(user.username)
 
                else:
 
                    if k == 'firstname':
 
                        k = 'name'
 
                    setattr(user, k, v)
 
            self.sa.add(user)
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def update_user(self, user, **kwargs):
 
        from rhodecode.lib.auth import get_crypt_password
 
        try:
 
            user = self._get_user(user)
 
            if user.username == 'default':
 
                raise DefaultUserException(
 
                    _("You can't Edit this user since it's"
 
                      " crucial for entire application")
 
                )
 

	
 
            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)
 
            return user
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def update_my_account(self, user_id, form_data):
 
        from rhodecode.lib.auth import get_crypt_password
 
        try:
 
            user = self.get(user_id, cache=False)
 
            if user.username == 'default':
 
                raise DefaultUserException(
 
                    _("You can't Edit this user since it's"
 
                      " crucial for entire application")
 
                )
 
            for k, v in form_data.items():
 
                if k == 'new_password' and v:
 
                    user.password = get_crypt_password(v)
 
                    user.api_key = generate_api_key(user.username)
 
                else:
 
                    if k == 'firstname':
 
                        k = 'name'
 
                    if k not in ['admin', 'active']:
 
                        setattr(user, k, v)
 

	
 
            self.sa.add(user)
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def delete(self, user):
 
        user = self._get_user(user)
 

	
 
        try:
 
            if user.username == 'default':
 
                raise DefaultUserException(
 
                    _(u"You can't remove this user since it's"
 
                      " crucial for entire application")
 
                )
 
            if user.repositories:
 
                repos = [x.repo_name for x in user.repositories]
 
                raise UserOwnsReposException(
 
                    _(u'user "%s" still owns %s repositories and cannot be '
 
                      'removed. Switch owners or remove those repositories. %s')
 
                    % (user.username, len(repos), ', '.join(repos))
 
                )
 
            self.sa.delete(user)
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def reset_password_link(self, data):
 
        from rhodecode.lib.celerylib import tasks, run_task
 
        run_task(tasks.send_password_link, data['email'])
 

	
 
    def reset_password(self, data):
 
        from rhodecode.lib.celerylib import tasks, run_task
 
        run_task(tasks.reset_user_password, data['email'])
 

	
 
    def fill_data(self, auth_user, user_id=None, api_key=None):
 
        """
 
        Fetches auth_user by user_id,or api_key if present.
 
        Fills auth_user attributes with those taken from database.
 
        Additionally set's is_authenitated if lookup fails
 
        present in database
 

	
 
        :param auth_user: instance of user to set attributes
 
        :param user_id: user id to fetch by
 
        :param api_key: api key to fetch by
 
        """
 
        if user_id is None and api_key is None:
 
            raise Exception('You need to pass user_id or api_key')
 

	
 
        try:
 
            if api_key:
 
                dbuser = self.get_by_api_key(api_key)
 
            else:
 
                dbuser = self.get(user_id)
 

	
 
            if dbuser is not None and dbuser.active:
 
                log.debug('filling %s data' % dbuser)
 
                for k, v in dbuser.get_dict().items():
 
                    setattr(auth_user, k, v)
 
            else:
 
                return False
 

	
 
        except:
 
            log.error(traceback.format_exc())
 
            auth_user.is_authenticated = False
 
            return False
 

	
 
        return True
 

	
 
    def fill_perms(self, user):
 
        """
 
        Fills user permission attribute with permissions taken from database
 
        works for permissions given for repositories, and for permissions that
 
        are granted to groups
 

	
 
        :param user: user instance to fill his perms
 
        """
 
        RK = 'repositories'
rhodecode/tests/api/api_base.py
Show inline comments
 
@@ -280,193 +280,193 @@ class BaseTestApi(object):
 
                                           password=u'qweqwe',
 
                                           email=u'u232@rhodecode.org',
 
                                           firstname=u'u1', lastname=u'u1')
 
        self.Session().commit()
 
        username = usr.username
 
        email = usr.email
 
        usr_id = usr.user_id
 
        ## DELETE THIS USER NOW
 

	
 
        id_, params = _build_data(self.apikey, 'delete_user',
 
                                  userid=username,)
 
        response = self.app.post(API_URL, content_type='application/json',
 
                                 params=params)
 

	
 
        ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
 
               'user': None}
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 

	
 
    @mock.patch.object(UserModel, 'delete', crash)
 
    def test_api_delete_user_when_exception_happened(self):
 
        usr = UserModel().create_or_update(username=u'test_user',
 
                                           password=u'qweqwe',
 
                                           email=u'u232@rhodecode.org',
 
                                           firstname=u'u1', lastname=u'u1')
 
        self.Session().commit()
 
        username = usr.username
 

	
 
        id_, params = _build_data(self.apikey, 'delete_user',
 
                                  userid=username,)
 
        response = self.app.post(API_URL, content_type='application/json',
 
                                 params=params)
 
        ret = 'failed to delete ID:%s %s' % (usr.user_id,
 
                                             usr.username)
 
        expected = ret
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    @parameterized.expand([('firstname', 'new_username'),
 
                           ('lastname', 'new_username'),
 
                           ('email', 'new_username'),
 
                           ('admin', True),
 
                           ('admin', False),
 
                           ('ldap_dn', 'test'),
 
                           ('ldap_dn', None),
 
                           ('active', False),
 
                           ('active', True),
 
                           ('password', 'newpass')
 
                           ])
 
    def test_api_update_user(self, name, expected):
 
        usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
 
        kw = {name: expected,
 
              'userid': usr.user_id}
 
        id_, params = _build_data(self.apikey, 'update_user', **kw)
 
        response = self.app.post(API_URL, content_type='application/json',
 
                                 params=params)
 

	
 
        ret = {
 
        'msg': 'updated user ID:%s %s' % (usr.user_id, self.TEST_USER_LOGIN),
 
        'user': jsonify(UserModel()\
 
                            .get_by_username(self.TEST_USER_LOGIN)\
 
                            .get_api_data())
 
        }
 

	
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 

	
 
    def test_api_update_user_no_changed_params(self):
 
        usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
 
        ret = jsonify(usr.get_api_data())
 
        id_, params = _build_data(self.apikey, 'update_user',
 
                                  userid=TEST_USER_ADMIN_LOGIN)
 

	
 
        response = self.app.post(API_URL, content_type='application/json',
 
                                 params=params)
 
        ret = {
 
        'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN),
 
        'user': ret
 
        }
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 

	
 
    def test_api_update_user_by_user_id(self):
 
        usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
 
        ret = jsonify(usr.get_api_data())
 
        id_, params = _build_data(self.apikey, 'update_user',
 
                                  userid=usr.user_id)
 

	
 
        response = self.app.post(API_URL, content_type='application/json',
 
                                 params=params)
 
        ret = {
 
        'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN),
 
        'user': ret
 
        }
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 

	
 
    @mock.patch.object(UserModel, 'create_or_update', crash)
 
    @mock.patch.object(UserModel, 'update_user', crash)
 
    def test_api_update_user_when_exception_happens(self):
 
        usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
 
        ret = jsonify(usr.get_api_data())
 
        id_, params = _build_data(self.apikey, 'update_user',
 
                                  userid=usr.user_id)
 

	
 
        response = self.app.post(API_URL, content_type='application/json',
 
                                 params=params)
 
        ret = 'failed to update user `%s`' % usr.user_id
 

	
 
        expected = ret
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    def test_api_get_repo(self):
 
        new_group = 'some_new_group'
 
        make_users_group(new_group)
 
        RepoModel().grant_users_group_permission(repo=self.REPO,
 
                                                 group_name=new_group,
 
                                                 perm='repository.read')
 
        self.Session().commit()
 
        id_, params = _build_data(self.apikey, 'get_repo',
 
                                  repoid=self.REPO)
 
        response = self.app.post(API_URL, content_type='application/json',
 
                                 params=params)
 

	
 
        repo = RepoModel().get_by_repo_name(self.REPO)
 
        ret = repo.get_api_data()
 

	
 
        members = []
 
        for user in repo.repo_to_perm:
 
            perm = user.permission.permission_name
 
            user = user.user
 
            user_data = user.get_api_data()
 
            user_data['type'] = "user"
 
            user_data['permission'] = perm
 
            members.append(user_data)
 

	
 
        for users_group in repo.users_group_to_perm:
 
            perm = users_group.permission.permission_name
 
            users_group = users_group.users_group
 
            users_group_data = users_group.get_api_data()
 
            users_group_data['type'] = "users_group"
 
            users_group_data['permission'] = perm
 
            members.append(users_group_data)
 

	
 
        ret['members'] = members
 

	
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 
        destroy_users_group(new_group)
 

	
 
    def test_api_get_repo_that_doesn_not_exist(self):
 
        id_, params = _build_data(self.apikey, 'get_repo',
 
                                  repoid='no-such-repo')
 
        response = self.app.post(API_URL, content_type='application/json',
 
                                 params=params)
 

	
 
        ret = 'repository `%s` does not exist' % 'no-such-repo'
 
        expected = ret
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    def test_api_get_repos(self):
 
        id_, params = _build_data(self.apikey, 'get_repos')
 
        response = self.app.post(API_URL, content_type='application/json',
 
                                 params=params)
 

	
 
        result = []
 
        for repo in RepoModel().get_all():
 
            result.append(repo.get_api_data())
 
        ret = jsonify(result)
 

	
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 

	
 
    @parameterized.expand([('all', 'all'),
 
                           ('dirs', 'dirs'),
 
                           ('files', 'files'), ])
 
    def test_api_get_repo_nodes(self, name, ret_type):
 
        rev = 'tip'
 
        path = '/'
 
        id_, params = _build_data(self.apikey, 'get_repo_nodes',
 
                                  repoid=self.REPO, revision=rev,
 
                                  root_path=path,
 
                                  ret_type=ret_type)
 
        response = self.app.post(API_URL, content_type='application/json',
 
                                 params=params)
 

	
 
        # we don't the actual return types here since it's tested somewhere
 
        # else
 
        expected = json.loads(response.body)['result']
 
        self._compare_ok(id_, expected, given=response.body)
 

	
 
    def test_api_get_repo_nodes_bad_revisions(self):
 
        rev = 'i-dont-exist'
 
        path = '/'
 
        id_, params = _build_data(self.apikey, 'get_repo_nodes',
0 comments (0 inline, 0 general)