Пример #1
0
    def test_extra_email_map(self):
        usr = UserModel().create_or_update(username=u'test_user',
                                           password=u'qweqwe',
                                     email=u'*****@*****.**',
                                     firstname=u'u1', lastname=u'u1')
        Session().commit()

        m = UserEmailMap()
        m.email = u'*****@*****.**'
        m.user = usr
        Session().add(m)
        Session().commit()

        u = User.get_by_email(email='*****@*****.**')
        self.assertEqual(usr.user_id, u.user_id)
        self.assertEqual(usr.username, u.username)

        u = User.get_by_email(email='*****@*****.**')
        self.assertEqual(usr.user_id, u.user_id)
        self.assertEqual(usr.username, u.username)
        u = User.get_by_email(email='*****@*****.**')
        self.assertEqual(None, u)

        UserModel().delete(usr.user_id)
        Session().commit()
Пример #2
0
    def reset_password(self, data):
        from rhodecode.lib.celerylib import tasks, run_task
        from rhodecode.lib import auth
        user_email = data['email']
        try:
            try:
                user = User.get_by_email(user_email)
                new_passwd = auth.PasswordGenerator().gen_password(8,
                                 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)
                if new_passwd is None:
                    raise Exception('unable to generate new password')
            except Exception:
                log.error(traceback.format_exc())
                Session().rollback()

            run_task(tasks.send_email, user_email,
                     _('Your new password'),
                     _('Your new RhodeCode password:%s') % (new_passwd))
            log.info('send new password mail to %s' % user_email)

        except Exception:
            log.error('Failed to update user password')
            log.error(traceback.format_exc())

        return True
Пример #3
0
def send_password_link(user_email):
    from rhodecode.model.notification import EmailNotificationModel

    log = get_logger(send_password_link)
    DBS = get_session()

    try:
        user = User.get_by_email(user_email)
        if user:
            log.debug('password reset user found %s' % user)
            link = url('reset_password_confirmation', key=user.api_key,
                       qualified=True)
            reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
            body = EmailNotificationModel().get_email_tmpl(reg_type,
                                                **{'user':user.short_contact,
                                                   'reset_url':link})
            log.debug('sending email')
            run_task(send_email, user_email,
                     _("password reset link"), body)
            log.info('send new password mail to %s' % user_email)
        else:
            log.debug("password reset email %s not found" % user_email)
    except:
        log.error(traceback.format_exc())
        return False

    return True
Пример #4
0
def reset_user_password(user_email):
    from rhodecode.lib import auth

    log = get_logger(reset_user_password)
    DBS = get_session()

    try:
        try:
            user = User.get_by_email(user_email)
            new_passwd = auth.PasswordGenerator().gen_password(8,
                             auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
            if user:
                user.password = auth.get_crypt_password(new_passwd)
                user.api_key = auth.generate_api_key(user.username)
                DBS.add(user)
                DBS.commit()
                log.info('change password for %s' % user_email)
            if new_passwd is None:
                raise Exception('unable to generate new password')
        except:
            log.error(traceback.format_exc())
            DBS.rollback()

        run_task(send_email, user_email,
                 'Your new password',
                 'Your new RhodeCode password:%s' % (new_passwd))
        log.info('send new password mail to %s' % user_email)

    except:
        log.error('Failed to update user password')
        log.error(traceback.format_exc())

    return True
Пример #5
0
    def reset_password_link(self, data):
        from rhodecode.lib.celerylib import tasks, run_task
        from rhodecode.model.notification import EmailNotificationModel
        user_email = data['email']
        try:
            user = User.get_by_email(user_email)
            if user:
                log.debug('password reset user found %s' % user)
                link = url('reset_password_confirmation', key=user.api_key,
                           qualified=True)
                reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
                body = EmailNotificationModel().get_email_tmpl(reg_type,
                                                    **{'user': user.short_contact,
                                                       'reset_url': link})
                log.debug('sending email')
                run_task(tasks.send_email, user_email,
                         _("Password reset link"), body, body)
                log.info('send new password mail to %s' % user_email)
            else:
                log.debug("password reset email %s not found" % user_email)
        except Exception:
            log.error(traceback.format_exc())
            return False

        return True
Пример #6
0
    def reset_password_link(self, data):
        from rhodecode.lib.celerylib import tasks, run_task
        from rhodecode.model.notification import EmailNotificationModel
        user_email = data['email']
        try:
            user = User.get_by_email(user_email)
            if user:
                log.debug('password reset user found %s' % user)
                link = url('reset_password_confirmation',
                           key=user.api_key,
                           qualified=True)
                reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
                body = EmailNotificationModel().get_email_tmpl(
                    reg_type, **{
                        'user': user.short_contact,
                        'reset_url': link
                    })
                log.debug('sending email')
                run_task(tasks.send_email, user_email,
                         _("Password reset link"), body, body)
                log.info('send new password mail to %s' % user_email)
            else:
                log.debug("password reset email %s not found" % user_email)
        except Exception:
            log.error(traceback.format_exc())
            return False

        return True
Пример #7
0
    def reset_password(self, data):
        from rhodecode.lib.celerylib import tasks, run_task
        from rhodecode.lib import auth
        user_email = data['email']
        try:
            try:
                user = User.get_by_email(user_email)
                new_passwd = auth.PasswordGenerator().gen_password(
                    8, 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)
                if new_passwd is None:
                    raise Exception('unable to generate new password')
            except Exception:
                log.error(traceback.format_exc())
                Session().rollback()

            run_task(tasks.send_email, user_email, _('Your new password'),
                     _('Your new RhodeCode password:%s') % (new_passwd))
            log.info('send new password mail to %s' % user_email)

        except Exception:
            log.error('Failed to update user password')
            log.error(traceback.format_exc())

        return True
Пример #8
0
    def reset_password(self, data):
        from rhodecode.lib.celerylib import tasks, run_task
        from rhodecode.lib import auth
        user_email = data['email']
        pre_db = True
        try:
            user = User.get_by_email(user_email)
            new_passwd = auth.PasswordGenerator().gen_password(
                8, 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)
            if new_passwd is None:
                raise Exception('unable to generate new password')

            pre_db = False
            run_task(tasks.send_email, user_email, _('Your new password'),
                     _('Your new RhodeCode password:%s') % (new_passwd, ))
            log.info('send new password mail to %s' % user_email)

        except Exception:
            log.error('Failed to update user password')
            log.error(traceback.format_exc())
            if pre_db:
                # we rollback only if local db stuff fails. If it goes into
                # run_task, we're pass rollback state this wouldn't work then
                Session().rollback()

        return True
Пример #9
0
 def validate_python(self, value, state):
     user = User.get_by_email(value, case_insensitive=True)
     if user is None:
         msg = M(self, 'non_existing_email', state, email=value)
         raise formencode.Invalid(msg, value, state,
             error_dict=dict(email=msg)
         )
Пример #10
0
def test_extra_email_map(test_user):

    m = UserEmailMap()
    m.email = u'*****@*****.**'
    m.user = test_user
    Session().add(m)
    Session().commit()

    u = User.get_by_email(email='*****@*****.**')
    assert test_user.user_id == u.user_id
    assert test_user.username == u.username

    u = User.get_by_email(email='*****@*****.**')
    assert test_user.user_id == u.user_id
    assert test_user.username == u.username
    u = User.get_by_email(email='*****@*****.**')
    assert u is None
Пример #11
0
 def validate_python(self, value, state):
     if (old_data.get('email') or '').lower() != value:
         user = User.get_by_email(value, case_insensitive=True)
         if user:
             msg = M(self, 'email_taken', state)
             raise formencode.Invalid(msg, value, state,
                 error_dict=dict(email=msg)
             )
Пример #12
0
 def to_python(self, value, state):
     value = value.lower()
     if (old_data.get('email') or '').lower() != value:
         user = User.get_by_email(value, case_insensitive=True)
         if user:
             raise formencode.Invalid(
                 _("This e-mail address is already taken"), value, state
             )
     return value
Пример #13
0
    def to_python(self, value, state):
        value = value.lower()
        user = User.get_by_email(value, case_insensitive=True)
        if  user is None:
            raise formencode.Invalid(
                _("This e-mail address doesn't exist."), value, state
            )

        return value
Пример #14
0
    def password_reset(self):
        settings = SettingsModel().get_all_settings()
        captcha_private_key = settings.get('rhodecode_captcha_private_key')
        captcha_active = bool(captcha_private_key)
        captcha_public_key = settings.get('rhodecode_captcha_public_key')

        render_ctx = {
            'captcha_active': captcha_active,
            'captcha_public_key': captcha_public_key,
            'defaults': {},
            'errors': {},
        }

        if self.request.POST:
            password_reset_form = PasswordResetForm()()
            try:
                form_result = password_reset_form.to_python(
                    self.request.params)
                if captcha_active:
                    response = submit(
                        self.request.params.get('recaptcha_challenge_field'),
                        self.request.params.get('recaptcha_response_field'),
                        private_key=captcha_private_key,
                        remoteip=get_ip_addr(self.request.environ))
                    if captcha_active and not response.is_valid:
                        _value = form_result
                        _msg = _('bad captcha')
                        error_dict = {'recaptcha_field': _msg}
                        raise formencode.Invalid(_msg, _value, None,
                                                 error_dict=error_dict)

                # Generate reset URL and send mail.
                user_email = form_result['email']
                user = User.get_by_email(user_email)
                password_reset_url = self.request.route_url(
                    'reset_password_confirmation',
                    _query={'key': user.api_key})
                UserModel().reset_password_link(
                    form_result, password_reset_url)

                # Display success message and redirect.
                self.session.flash(
                    _('Your password reset link was sent'),
                    queue='success')
                return HTTPFound(self.request.route_path('login'))

            except formencode.Invalid as errors:
                render_ctx.update({
                    'defaults': errors.value,
                    'errors': errors.error_dict,
                })

        return render_ctx
Пример #15
0
    def reset_password(self, data):
        from rhodecode.lib.celerylib import tasks, run_task
        from rhodecode.model.notification import EmailNotificationModel
        from rhodecode.lib import auth
        user_email = data['email']
        pre_db = True
        try:
            user = User.get_by_email(user_email)
            new_passwd = auth.PasswordGenerator().gen_password(
                12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
            if user:
                user.password = auth.get_crypt_password(new_passwd)
                # also force this user to reset his password !
                user.update_userdata(force_password_change=True)

                Session().add(user)
                Session().commit()
                log.info('change password for %s', user_email)
            if new_passwd is None:
                raise Exception('unable to generate new password')

            pre_db = False

            email_kwargs = {
                'new_password': new_passwd,
                'user': user,
                'email': user_email,
                'date': datetime.datetime.now()
            }

            (subject, headers, email_body,
             email_body_plaintext) = EmailNotificationModel().render_email(
                EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION, **email_kwargs)

            recipients = [user_email]

            action_logger_generic(
                'sent new password to user: {} with email: {}'.format(
                    user, user_email), namespace='security.password_reset')

            run_task(tasks.send_email, recipients, subject,
                     email_body_plaintext, email_body)

        except Exception:
            log.error('Failed to update user password')
            log.error(traceback.format_exc())
            if pre_db:
                # we rollback only if local db stuff fails. If it goes into
                # run_task, we're pass rollback state this wouldn't work then
                Session().rollback()

        return True
Пример #16
0
def person(author, show_attr="username_and_name"):
    # attr to return from fetched user
    person_getter = lambda usr: getattr(usr, show_attr)

    # Valid email in the attribute passed, see if they're in the system
    _email = email(author)
    if _email != '':
        user = User.get_by_email(_email, case_insensitive=True, cache=True)
        if user is not None:
            return person_getter(user)

    # Maybe it's a username?
    _author = author_name(author)
    user = User.get_by_username(_author, case_insensitive=True, cache=True)
    if user is not None:
        return person_getter(user)

    # Still nothing?  Just pass back the author name if any, else the email
    return _author or _email
Пример #17
0
def email_or_none(author):
    # extract email from the commit string
    _email = email(author)
    if _email != '':
        # check it against RhodeCode database, and use the MAIN email for this
        # user
        user = User.get_by_email(_email, case_insensitive=True, cache=True)
        if user is not None:
            return user.email
        return _email

    # See if it contains a username we can get an email from
    user = User.get_by_username(author_name(author), case_insensitive=True,
                                cache=True)
    if user is not None:
        return user.email

    # No valid email, not a valid user in the system, none!
    return None
Пример #18
0
def email_or_none(author):
    # extract email from the commit string
    _email = email(author)
    if _email != '':
        # check it against RhodeCode database, and use the MAIN email for this
        # user
        user = User.get_by_email(_email, case_insensitive=True, cache=True)
        if user is not None:
            return user.email
        return _email

    # See if it contains a username we can get an email from
    user = User.get_by_username(author_name(author),
                                case_insensitive=True,
                                cache=True)
    if user is not None:
        return user.email

    # No valid email, not a valid user in the system, none!
    return None
Пример #19
0
def person(author, show_attr="username_and_name"):
    # attr to return from fetched user
    person_getter = lambda usr: getattr(usr, show_attr)

    # Valid email in the attribute passed, see if they're in the system
    _email = email(author)
    if _email != '':
        user = User.get_by_email(_email, case_insensitive=True, cache=True)
        if user is not None:
            return person_getter(user)

    # Maybe it's a username?
    _author = author_name(author)
    user = User.get_by_username(_author, case_insensitive=True,
                                cache=True)
    if user is not None:
        return person_getter(user)

    # Still nothing?  Just pass back the author name if any, else the email
    return _author or _email
Пример #20
0
    def create_user(self, apiuser, username, email, password, firstname=None,
                    lastname=None, active=True, admin=False, ldap_dn=None):
        """
        Create new user

        :param apiuser:
        :param username:
        :param password:
        :param email:
        :param name:
        :param lastname:
        :param active:
        :param admin:
        :param ldap_dn:
        """
        if User.get_by_username(username):
            raise JSONRPCError("user %s already exist" % username)

        if User.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:
            usr = UserModel().create_or_update(
                username, password, email, firstname,
                lastname, active, admin, ldap_dn
            )
            Session.commit()
            return dict(
                id=usr.user_id,
                msg='created new user %s' % username
            )
        except Exception:
            log.error(traceback.format_exc())
            raise JSONRPCError('failed to create user %s' % username)
Пример #21
0
    def reset_password(self, data):
        from rhodecode.lib.celerylib import tasks, run_task
        from rhodecode.lib import auth

        user_email = data["email"]
        pre_db = True
        try:
            user = User.get_by_email(user_email)
            new_passwd = auth.PasswordGenerator().gen_password(8, 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)
            if new_passwd is None:
                raise Exception("unable to generate new password")

            pre_db = False
            run_task(
                tasks.send_email,
                user_email,
                _("Your new password"),
                _("Your new RhodeCode password:%s") % (new_passwd,),
            )
            log.info("send new password mail to %s" % user_email)

        except Exception:
            log.error("Failed to update user password")
            log.error(traceback.format_exc())
            if pre_db:
                # we rollback only if local db stuff fails. If it goes into
                # run_task, we're pass rollback state this wouldn't work then
                Session().rollback()

        return True
Пример #22
0
    def reset_password_link(self, data, pwd_reset_url):
        from rhodecode.lib.celerylib import tasks, run_task
        from rhodecode.model.notification import EmailNotificationModel
        user_email = data['email']
        try:
            user = User.get_by_email(user_email)
            if user:
                log.debug('password reset user found %s', user)

                email_kwargs = {
                    'password_reset_url': pwd_reset_url,
                    'user': user,
                    'email': user_email,
                    'date': datetime.datetime.now()
                }

                (subject, headers, email_body,
                 email_body_plaintext) = EmailNotificationModel().render_email(
                    EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)

                recipients = [user_email]

                action_logger_generic(
                    'sending password reset email to user: {}'.format(
                        user), namespace='security.password_reset')

                run_task(tasks.send_email, recipients, subject,
                         email_body_plaintext, email_body)

            else:
                log.debug("password reset email %s not found", user_email)
        except Exception:
            log.error(traceback.format_exc())
            return False

        return True
Пример #23
0
 def get_by_email(self, email, cache=False, case_insensitive=False):
     return User.get_by_email(email, case_insensitive, cache)
Пример #24
0
    def create(self, text, repo, user, revision=None, pull_request=None,
               f_path=None, line_no=None, status_change=None):
        """
        Creates new comment for changeset or pull request.
        IF status_change is not none this comment is associated with a
        status change of changeset or changesets associated with pull request

        :param text:
        :param repo:
        :param user:
        :param revision:
        :param pull_request:
        :param f_path:
        :param line_no:
        :param status_change:
        """
        if not text:
            return

        repo = self._get_repo(repo)
        user = self._get_user(user)
        comment = ChangesetComment()
        comment.repo = repo
        comment.author = user
        comment.text = text
        comment.f_path = f_path
        comment.line_no = line_no

        if revision:
            cs = repo.scm_instance.get_changeset(revision)
            desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
            author_email = cs.author_email
            comment.revision = revision
        elif pull_request:
            pull_request = self.__get_pull_request(pull_request)
            comment.pull_request = pull_request
            desc = pull_request.pull_request_id
        else:
            raise Exception('Please specify revision or pull_request_id')

        self.sa.add(comment)
        self.sa.flush()

        # make notification
        line = ''
        body = text

        #changeset
        if revision:
            if line_no:
                line = _('on line %s') % line_no
            subj = safe_unicode(
                h.link_to('Re commit: %(desc)s %(line)s' % \
                          {'desc': desc, 'line': line},
                          h.url('changeset_home', repo_name=repo.repo_name,
                                revision=revision,
                                anchor='comment-%s' % comment.comment_id,
                                qualified=True,
                          )
                )
            )
            notification_type = Notification.TYPE_CHANGESET_COMMENT
            # get the current participants of this changeset
            recipients = ChangesetComment.get_users(revision=revision)
            # add changeset author if it's in rhodecode system
            recipients += [User.get_by_email(author_email)]
            email_kwargs = {
                'status_change': status_change,
            }
        #pull request
        elif pull_request:
            _url = h.url('pullrequest_show',
                repo_name=pull_request.other_repo.repo_name,
                pull_request_id=pull_request.pull_request_id,
                anchor='comment-%s' % comment.comment_id,
                qualified=True,
            )
            subj = safe_unicode(
                h.link_to('Re pull request: %(desc)s %(line)s' % \
                          {'desc': desc, 'line': line}, _url)
            )

            notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
            # get the current participants of this pull request
            recipients = ChangesetComment.get_users(pull_request_id=
                                                pull_request.pull_request_id)
            # add pull request author
            recipients += [pull_request.author]

            # add the reviewers to notification
            recipients += [x.user for x in pull_request.reviewers]

            #set some variables for email notification
            email_kwargs = {
                'pr_id': pull_request.pull_request_id,
                'status_change': status_change,
                'pr_comment_url': _url,
                'pr_comment_user': h.person(user.email),
                'pr_target_repo': h.url('summary_home',
                                   repo_name=pull_request.other_repo.repo_name,
                                   qualified=True)
            }
        # create notification objects, and emails
        NotificationModel().create(
            created_by=user, subject=subj, body=body,
            recipients=recipients, type_=notification_type,
            email_kwargs=email_kwargs
        )

        mention_recipients = set(self._extract_mentions(body))\
                                .difference(recipients)
        if mention_recipients:
            email_kwargs.update({'pr_mention': True})
            subj = _('[Mention]') + ' ' + subj
            NotificationModel().create(
                created_by=user, subject=subj, body=body,
                recipients=mention_recipients,
                type_=notification_type,
                email_kwargs=email_kwargs
            )

        return comment
Пример #25
0
    def create(self,
               text,
               repo_id,
               user_id,
               revision,
               f_path=None,
               line_no=None):
        """
        Creates new comment for changeset

        :param text:
        :param repo_id:
        :param user_id:
        :param revision:
        :param f_path:
        :param line_no:
        """

        if text:
            repo = Repository.get(repo_id)
            cs = repo.scm_instance.get_changeset(revision)
            desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
            author_email = cs.author_email
            comment = ChangesetComment()
            comment.repo = repo
            comment.user_id = user_id
            comment.revision = revision
            comment.text = text
            comment.f_path = f_path
            comment.line_no = line_no

            self.sa.add(comment)
            self.sa.flush()
            # make notification
            line = ''
            if line_no:
                line = _('on line %s') % line_no
            subj = safe_unicode(
                h.link_to('Re commit: %(commit_desc)s %(line)s' % \
                          {'commit_desc': desc, 'line': line},
                          h.url('changeset_home', repo_name=repo.repo_name,
                                revision=revision,
                                anchor='comment-%s' % comment.comment_id,
                                qualified=True,
                          )
                )
            )

            body = text

            # get the current participants of this changeset
            recipients = ChangesetComment.get_users(revision=revision)

            # add changeset author if it's in rhodecode system
            recipients += [User.get_by_email(author_email)]

            NotificationModel().create(
                created_by=user_id,
                subject=subj,
                body=body,
                recipients=recipients,
                type_=Notification.TYPE_CHANGESET_COMMENT)

            mention_recipients = set(self._extract_mentions(body))\
                                    .difference(recipients)
            if mention_recipients:
                subj = _('[Mention]') + ' ' + subj
                NotificationModel().create(
                    created_by=user_id,
                    subject=subj,
                    body=body,
                    recipients=mention_recipients,
                    type_=Notification.TYPE_CHANGESET_COMMENT)

            return comment
Пример #26
0
 def get_by_email(self, email, cache=False, case_insensitive=False):
     return User.get_by_email(email, case_insensitive, cache)
Пример #27
0
    def create(self, text, repo_id, user_id, revision, f_path=None,
               line_no=None):
        """
        Creates new comment for changeset

        :param text:
        :param repo_id:
        :param user_id:
        :param revision:
        :param f_path:
        :param line_no:
        """

        if text:
            repo = Repository.get(repo_id)
            cs = repo.scm_instance.get_changeset(revision)
            desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
            author_email = cs.author_email
            comment = ChangesetComment()
            comment.repo = repo
            comment.user_id = user_id
            comment.revision = revision
            comment.text = text
            comment.f_path = f_path
            comment.line_no = line_no

            self.sa.add(comment)
            self.sa.flush()
            # make notification
            line = ''
            if line_no:
                line = _('on line %s') % line_no
            subj = safe_unicode(
                h.link_to('Re commit: %(commit_desc)s %(line)s' % \
                          {'commit_desc': desc, 'line': line},
                          h.url('changeset_home', repo_name=repo.repo_name,
                                revision=revision,
                                anchor='comment-%s' % comment.comment_id,
                                qualified=True,
                          )
                )
            )

            body = text

            # get the current participants of this changeset
            recipients = ChangesetComment.get_users(revision=revision)

            # add changeset author if it's in rhodecode system
            recipients += [User.get_by_email(author_email)]

            NotificationModel().create(
              created_by=user_id, subject=subj, body=body,
              recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT
            )

            mention_recipients = set(self._extract_mentions(body))\
                                    .difference(recipients)
            if mention_recipients:
                subj = _('[Mention]') + ' ' + subj
                NotificationModel().create(
                    created_by=user_id, subject=subj, body=body,
                    recipients=mention_recipients,
                    type_=Notification.TYPE_CHANGESET_COMMENT
                )

            return comment