Ejemplo n.º 1
0
    def reset_password_by_email(self):
        user = self.model
        if not user.email:
            raise ValueError('User has no email')

        prp = self.generate_password_reset_profile()

        later(
            current_app.tasks['sendmail'].delay,
            user.email,
            render_template('email/password_reset_subject.txt'),
            body={
                'plain':
                render_template('email/password_reset.txt',
                                activation_key=prp.activation_key,
                                user=user),
                'html':
                render_template('email/password_reset.html',
                                activation_key=prp.activation_key,
                                user=user),
            },
            headers={
                'X-Postmaster-Msgtype':
                current_app.config['EMAIL_MSGTYPES']['reset_password']
            },
        )

        return prp
Ejemplo n.º 2
0
def abuse_common(target_type, target):
    # FIXME: это всё не в bl, нехорошо (но при переносе надо не забыть про проверки доступа)

    user = current_user._get_current_object()
    if not target or not user.is_authenticated:
        abort(404)

    data = {
        'page_title': 'Отправка жалобы',
        'target_type': target_type,
        'target': target,
        'next': request.referrer or url_for('index.index'),
        'abuse': None,
        'can_abuse': True,
        'reason': '',
        'errors': [],
    }

    # Если пользователь уже отправлял жалобу, то он не может отправить её ещё раз
    abuse = list(AbuseReport.select(
        lambda x: x.target_type == target_type and x.target_id == target.id and x.user.id == user.id and not x.ignored
    ))
    abuse.sort(key=lambda x: -x.id)
    abuse = abuse[0] if abuse else None
    if abuse:
        data['abuse'] = abuse
        data['can_abuse'] = False

    if not abuse and request.method == 'POST':
        reason = request.form.get('reason') or ''
        data['reason'] = reason

        if not reason.strip():
            data['errors'].append('Причина жалобы не может отсутствовать')

        elif len(reason) > 8192:
            data['errors'].append('Слишком длинно; опишите причину покороче')

        else:
            abuse = AbuseReport(
                target_type=target_type,
                target_id=target.id,
                user=user,
                reason=request.form['reason'],
            )
            abuse.flush()

            # Если это первая жалоба на объект, то уведомляем админов
            if AbuseReport.select(
                lambda x: x.target_type == target_type and x.target_id == target.id and not x.ignored and x.resolved_at is None
            ).count() == 1:
                later(current_app.tasks['notify_abuse_report'].delay, abuse.id)

            return redirect(request.form.get('next') or url_for('index.index'), 302)

    if g.is_ajax:
        return jsonify({'page_content': {'modal': render_template('abuse_report_ajax.html', **data)}})
    return render_template('abuse_report.html', **data)
Ejemplo n.º 3
0
    def delete(self, author):
        from mini_fiction.models import AdminLog

        htmlblock = self.model
        if htmlblock.is_template and not author.is_superuser:
            raise ValidationError({'is_template': [lazy_gettext('Access denied')]})
        later(self.clear_cache, htmlblock.name)
        AdminLog.bl.create(user=author, obj=htmlblock, action=AdminLog.DELETION)
        htmlblock.delete()
Ejemplo n.º 4
0
    def create(self, *args, **kwargs):
        comment = super().create(*args, **kwargs)
        later(current_app.tasks['notify_story_lcomment'].delay, comment.id)

        assert comment.author
        act = comment.local.story.bl.get_activity(comment.author)
        if act:
            act.last_local_comments += 1

        return comment
Ejemplo n.º 5
0
    def create(self, *args, **kwargs):
        comment = super().create(*args, **kwargs)
        later(current_app.tasks['notify_story_lcomment'].delay, comment.id)

        assert comment.author
        act = comment.local.story.bl.get_activity(comment.author)
        if act:
            act.last_local_comments += 1

        return comment
Ejemplo n.º 6
0
    def set_blacklist(self, user, reason):
        from mini_fiction.models import StoryTag, StoryTagLog, AdminLog

        if not user or not user.is_staff:
            raise ValueError('Not authorized')

        tag = self.model
        if reason == tag.reason_to_blacklist:
            return

        old_reason = tag.reason_to_blacklist
        tm = datetime.utcnow()

        if reason:
            tag.reason_to_blacklist = reason
            tag.is_alias_for = None
            tag.is_hidden_alias = False

            story_tags = list(
                StoryTag.select(lambda x: x.tag == tag).prefetch(
                    StoryTag.story))
            for st in story_tags:
                StoryTagLog(
                    story=st.story.id,
                    tag=tag,
                    tag_name=tag.name,
                    action_flag=StoryTagLog.DELETION,
                    modified_by=user,
                    date=tm,
                ).flush()
                # FIXME: кажется, следующая строчка должна находиться не здесь, но я ленивый
                later(current_app.tasks['sphinx_update_story'].delay,
                      st.story.id, ('tag', ))
                st.delete()
                tag.stories_count -= 1
                if st.story.published:
                    tag.published_stories_count -= 1

        else:
            tag.reason_to_blacklist = ''

        tag.updated_at = tm

        if tag.reason_to_blacklist and old_reason:
            log_message = 'Изменена причина попадания тега в чёрный список.'
        elif tag.reason_to_blacklist:
            log_message = 'Тег добавлен в чёрный список.'
        else:
            log_message = 'Тег убран из чёрного списка.'
        AdminLog.bl.create(
            user=user,
            obj=tag,
            action=AdminLog.CHANGE,
            change_message=log_message,
        )
Ejemplo n.º 7
0
    def create(self, *args, **kwargs):
        comment = super().create(*args, **kwargs)
        later(current_app.tasks['notify_story_comment'].delay, comment.id)
        later(current_app.tasks['sphinx_update_comments_count'].delay, comment.story.id)

        if comment.author:
            act = comment.story.bl.get_activity(comment.author)
            if act:
                act.last_comments += 1

        return comment
Ejemplo n.º 8
0
    def update_email_with_confirmation(self, email):
        from mini_fiction.models import Author, ChangeEmailProfile

        user = self.model

        cep = ChangeEmailProfile.get(user=user)
        cep_expired = not cep or cep.date + timedelta(days=current_app.config['ACCOUNT_ACTIVATION_DAYS']) < datetime.utcnow()

        if email.lower() == user.email.lower():
            if cep:
                # Отмена установки новой почты
                cep.delete()
            return False

        if Author.bl.is_email_busy(email):
            raise ValidationError({'email': [lazy_gettext('Email address is already in use')]})

        data = {
            'date': datetime.utcnow(),
            'activation_key': utils_random.random_string(40),
            'user': user,
            'email': email,
        }

        if not cep:
            cep = ChangeEmailProfile(**data)
        elif cep_expired or email.lower() != cep.email.lower():
            for k, v in data.items():
                setattr(cep, k, v)

        later(
            current_app.tasks['sendmail'].delay,
            cep.email,
            render_template('email/change_email_subject.txt'),
            body={
                'plain': render_template('email/change_email.txt', activation_key=cep.activation_key, user=user),
                'html': render_template('email/change_email.html', activation_key=cep.activation_key, user=user),
            },
            headers={'X-Postmaster-Msgtype': current_app.config['EMAIL_MSGTYPES']['change_email']},
        )

        if user.email:
            later(
                current_app.tasks['sendmail'].delay,
                user.email,
                render_template('email/change_email_warning_subject.txt'),
                body={
                    'plain': render_template('email/change_email_warning.txt', user=user, new_email=email),
                    'html': render_template('email/change_email_warning.html', user=user, new_email=email),
                },
                headers={'X-Postmaster-Msgtype': current_app.config['EMAIL_MSGTYPES']['change_email_warning']},
            )

        return True
Ejemplo n.º 9
0
    def create(self, *args, **kwargs):
        comment = super().create(*args, **kwargs)
        later(current_app.tasks['notify_story_comment'].delay, comment.id)
        later(current_app.tasks['sphinx_update_comments_count'].delay,
              comment.story.id)

        if comment.author:
            act = comment.story.bl.get_activity(comment.author)
            if act:
                act.last_comments += 1

        return comment
Ejemplo n.º 10
0
    def set_blacklist(self, user, reason):
        from mini_fiction.models import StoryTag, StoryTagLog, AdminLog

        if not user or not user.is_staff:
            raise ValueError('Not authorized')

        tag = self.model
        if reason == tag.reason_to_blacklist:
            return

        old_reason = tag.reason_to_blacklist
        tm = datetime.utcnow()

        if reason:
            tag.reason_to_blacklist = reason
            tag.is_alias_for = None
            tag.is_hidden_alias = False

            story_tags = list(StoryTag.select(lambda x: x.tag == tag).prefetch(StoryTag.story))
            for st in story_tags:
                StoryTagLog(
                    story=st.story.id,
                    tag=tag,
                    tag_name=tag.name,
                    action_flag=StoryTagLog.DELETION,
                    modified_by=user,
                    date=tm,
                ).flush()
                # FIXME: кажется, следующая строчка должна находиться не здесь, но я ленивый
                later(current_app.tasks['sphinx_update_story'].delay, st.story.id, ('tag',))
                st.delete()
                tag.stories_count -= 1
                if st.story.published:
                    tag.published_stories_count -= 1

        else:
            tag.reason_to_blacklist = ''

        tag.updated_at = tm

        if tag.reason_to_blacklist and old_reason:
            log_message = 'Изменена причина попадания тега в чёрный список.'
        elif tag.reason_to_blacklist:
            log_message = 'Тег добавлен в чёрный список.'
        else:
            log_message = 'Тег убран из чёрного списка.'
        AdminLog.bl.create(
            user=user,
            obj=tag,
            action=AdminLog.CHANGE,
            change_message=log_message,
        )
Ejemplo n.º 11
0
    def register(self, data):
        from mini_fiction.models import RegistrationProfile

        if current_app.captcha:
            current_app.captcha.check_or_raise(data)

        data = Validator(REGISTRATION).validated(data)
        errors = self._check_existence(data['username'], data['email'])
        if errors:
            raise ValidationError(errors)

        old_rp = RegistrationProfile.get(username=data['username'],
                                         email=data['email'])
        if old_rp:
            old_rp.delete()
        del old_rp

        rp = RegistrationProfile(
            activation_key=utils_random.random_string(40),
            email=data['email'],
            password=self.generate_password_hash(data['password']),
            username=data['username'],
        )
        rp.flush()

        later(
            current_app.tasks['sendmail'].delay,
            data['email'],
            render_template('email/activation_subject.txt'),
            body={
                'plain':
                render_template('email/activation.txt',
                                activation_key=rp.activation_key),
                'html':
                render_template('email/activation.html',
                                activation_key=rp.activation_key),
            },
            headers={
                'X-Postmaster-Msgtype':
                current_app.config['EMAIL_MSGTYPES']['registration']
            },
        )

        return rp
Ejemplo n.º 12
0
    def reset_password_by_email(self):
        user = self.model
        if not user.email:
            raise ValueError('User has no email')

        prp = self.generate_password_reset_profile()

        later(
            current_app.tasks['sendmail'].delay,
            user.email,
            render_template('email/password_reset_subject.txt'),
            body={
                'plain': render_template('email/password_reset.txt', activation_key=prp.activation_key, user=user),
                'html': render_template('email/password_reset.html', activation_key=prp.activation_key, user=user),
            },
            headers={'X-Postmaster-Msgtype': current_app.config['EMAIL_MSGTYPES']['reset_password']},
        )

        return prp
Ejemplo n.º 13
0
    def update(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(HTML_BLOCK).validated(data, update=True)
        htmlblock = self.model

        if not author.is_superuser and (htmlblock.is_template or data.get('is_template')):
            raise ValidationError({'is_template': [lazy_gettext('Access denied')]})

        if ('name' in data and data['name'] != htmlblock.name) or ('lang' in data and data['lang'] != htmlblock.lang):
            raise ValidationError({
                'name': [lazy_gettext('Cannot change primary key')],
                'lang': [lazy_gettext('Cannot change primary key')],
            })

        if data.get('is_template', htmlblock.is_template) and 'content' in data:
            self.check_renderability(author, data.get('name', htmlblock.name), data['content'])

        changed_fields = set()
        old_name = htmlblock.name

        for key, value in data.items():
            if getattr(htmlblock, key) != value:
                setattr(htmlblock, key, value)
                changed_fields |= {key,}

        later(self.clear_cache, old_name)
        if htmlblock.name != old_name:
            later(self.clear_cache, htmlblock.name)

        if changed_fields:
            AdminLog.bl.create(
                user=author,
                obj=htmlblock,
                action=AdminLog.CHANGE,
                fields=sorted(changed_fields),
            )

        return htmlblock
Ejemplo n.º 14
0
    def create(self, author, data):
        from mini_fiction.models import AdminLog

        data = Validator(HTML_BLOCK).validated(data)

        if not author.is_superuser and data.get('is_template'):
            raise ValidationError({'is_template': [lazy_gettext('Access denied')]})

        if data.get('is_template'):
            self.check_renderability(author, data['name'], data['content'])

        if not data.get('lang'):
            data['lang'] = 'none'

        exist_htmlblock = self.model.get(name=data['name'], lang=data['lang'])
        if exist_htmlblock:
            raise ValidationError({'name': [lazy_gettext('Block already exists')]})

        htmlblock = self.model(**data)
        htmlblock.flush()
        AdminLog.bl.create(user=author, obj=htmlblock, action=AdminLog.ADDITION)
        later(self.clear_cache, htmlblock.name)  # avoid race condition by `later` (runs after commit)
        return htmlblock
Ejemplo n.º 15
0
    def register(self, data):
        from mini_fiction.models import RegistrationProfile

        if current_app.captcha:
            current_app.captcha.check_or_raise(data)

        data = Validator(REGISTRATION).validated(data)
        errors = self._check_existence(data['username'], data['email'])
        if errors:
            raise ValidationError(errors)

        old_rp = RegistrationProfile.get(username=data['username'], email=data['email'])
        if old_rp:
            old_rp.delete()
        del old_rp

        rp = RegistrationProfile(
            activation_key=utils_random.random_string(40),
            email=data['email'],
            password=self.generate_password_hash(data['password']),
            username=data['username'],
        )
        rp.flush()

        later(
            current_app.tasks['sendmail'].delay,
            data['email'],
            render_template('email/activation_subject.txt'),
            body={
                'plain': render_template('email/activation.txt', activation_key=rp.activation_key),
                'html': render_template('email/activation.html', activation_key=rp.activation_key),
            },
            headers={'X-Postmaster-Msgtype': current_app.config['EMAIL_MSGTYPES']['registration']},
        )

        return rp
Ejemplo n.º 16
0
 def create(self, *args, **kwargs):
     comment = super().create(*args, **kwargs)
     later(current_app.tasks['notify_news_comment'].delay, comment.id)
     return comment
Ejemplo n.º 17
0
 def create(self, *args, **kwargs):
     comment = super().create(*args, **kwargs)
     later(current_app.tasks['notify_news_comment'].delay, comment.id)
     return comment
Ejemplo n.º 18
0
    def make_alias_for(self, user, canonical_tag, hidden=False):
        from mini_fiction.models import StoryTag, StoryTagLog, AdminLog

        if not user or not user.is_staff:
            raise ValueError('Not authorized')

        if canonical_tag and canonical_tag.is_alias_for:
            if canonical_tag.is_alias_for.is_alias_for:
                raise RuntimeError(
                    'Tag alias {} refers to another alias {}!'.format(
                        canonical_tag.id, canonical_tag.is_alias_for.id))
            canonical_tag = canonical_tag.is_alias_for

        tag = self.model
        if tag.is_alias_for == canonical_tag:
            if canonical_tag and bool(hidden) != tag.is_hidden_alias:
                tag.is_hidden_alias = bool(hidden)
                tag.updated_at = datetime.utcnow()
                AdminLog.bl.create(
                    user=user,
                    obj=tag,
                    action=AdminLog.CHANGE,
                    fields=('is_hidden_alias', ),
                )
            return

        tm = datetime.utcnow()

        if canonical_tag:
            if canonical_tag == tag:
                raise RuntimeError('Self-reference!')

            tag.is_alias_for = canonical_tag
            tag.is_hidden_alias = bool(hidden)

            story_tags = list(
                StoryTag.select(lambda x: x.tag == tag).prefetch(
                    StoryTag.story))
            stories_with_canonical_tag = list(
                orm.select(x.story.id for x in StoryTag
                           if x.tag == canonical_tag))
            for st in story_tags:
                StoryTagLog(
                    story=st.story.id,
                    tag=tag,
                    tag_name=tag.name,
                    action_flag=StoryTagLog.DELETION,
                    modified_by=user,
                    date=tm,
                ).flush()
                if st.story.id not in stories_with_canonical_tag:
                    StoryTagLog(
                        story=st.story.id,
                        tag=canonical_tag,
                        tag_name=canonical_tag.name,
                        action_flag=StoryTagLog.ADDITION,
                        modified_by=user,
                        date=tm,
                    ).flush()
                    st.tag = canonical_tag
                    st.flush()
                    canonical_tag.stories_count += 1
                    if st.story.published:
                        canonical_tag.published_stories_count += 1
                else:
                    st.delete()
                later(current_app.tasks['sphinx_update_story'].delay,
                      st.story.id, ('tag', ))
                tag.stories_count -= 1
                if st.story.published:
                    tag.published_stories_count -= 1

        else:
            tag.is_alias_for = None
            tag.is_hidden_alias = False

        tag.updated_at = tm

        if tag.is_alias_for:
            log_message = 'Тег стал синонимом тега «{}».'.format(
                tag.is_alias_for.name)
        else:
            log_message = 'Тег перестал быть синонимом.'
        AdminLog.bl.create(
            user=user,
            obj=tag,
            action=AdminLog.CHANGE,
            change_message=log_message,
        )
Ejemplo n.º 19
0
def abuse_common(target_type, target):
    # FIXME: это всё не в bl, нехорошо (но при переносе надо не забыть про проверки доступа)

    user = current_user._get_current_object()
    if not target or not user.is_authenticated:
        abort(404)

    data = {
        'page_title': 'Отправка жалобы',
        'target_type': target_type,
        'target': target,
        'next': request.referrer or url_for('index.index'),
        'abuse': None,
        'can_abuse': True,
        'reason': '',
        'errors': [],
    }

    # Если пользователь уже отправлял жалобу, то он не может отправить её ещё раз
    abuse = list(
        AbuseReport.select(
            lambda x: x.target_type == target_type and x.target_id == target.id
            and x.user.id == user.id and not x.ignored))
    abuse.sort(key=lambda x: -x.id)
    abuse = abuse[0] if abuse else None
    if abuse:
        data['abuse'] = abuse
        data['can_abuse'] = False

    if not abuse and request.method == 'POST':
        reason = request.form.get('reason') or ''
        data['reason'] = reason

        if not reason.strip():
            data['errors'].append('Причина жалобы не может отсутствовать')

        elif len(reason) > 8192:
            data['errors'].append('Слишком длинно; опишите причину покороче')

        else:
            abuse = AbuseReport(
                target_type=target_type,
                target_id=target.id,
                user=user,
                reason=request.form['reason'],
            )
            abuse.flush()

            # Если это первая жалоба на объект, то уведомляем админов
            if AbuseReport.select(lambda x: x.target_type == target_type and x.
                                  target_id == target.id and not x.ignored and
                                  x.resolved_at is None).count() == 1:
                later(current_app.tasks['notify_abuse_report'].delay, abuse.id)

            return redirect(
                request.form.get('next') or url_for('index.index'), 302)

    if g.is_ajax:
        return jsonify({
            'page_content': {
                'modal': render_template('abuse_report_ajax.html', **data)
            }
        })
    return render_template('abuse_report.html', **data)
Ejemplo n.º 20
0
    def make_alias_for(self, user, canonical_tag, hidden=False):
        from mini_fiction.models import StoryTag, StoryTagLog, AdminLog

        if not user or not user.is_staff:
            raise ValueError('Not authorized')

        if canonical_tag and canonical_tag.is_alias_for:
            if canonical_tag.is_alias_for.is_alias_for:
                raise RuntimeError('Tag alias {} refers to another alias {}!'.format(canonical_tag.id, canonical_tag.is_alias_for.id))
            canonical_tag = canonical_tag.is_alias_for

        tag = self.model
        if tag.is_alias_for == canonical_tag:
            if canonical_tag and bool(hidden) != tag.is_hidden_alias:
                tag.is_hidden_alias = bool(hidden)
                tag.updated_at = datetime.utcnow()
                AdminLog.bl.create(
                    user=user,
                    obj=tag,
                    action=AdminLog.CHANGE,
                    fields=('is_hidden_alias',),
                )
            return

        tm = datetime.utcnow()

        if canonical_tag:
            if canonical_tag == tag:
                raise RuntimeError('Self-reference!')

            tag.is_alias_for = canonical_tag
            tag.is_hidden_alias = bool(hidden)

            story_tags = list(StoryTag.select(lambda x: x.tag == tag).prefetch(StoryTag.story))
            stories_with_canonical_tag = list(orm.select(x.story.id for x in StoryTag if x.tag == canonical_tag))
            for st in story_tags:
                StoryTagLog(
                    story=st.story.id,
                    tag=tag,
                    tag_name=tag.name,
                    action_flag=StoryTagLog.DELETION,
                    modified_by=user,
                    date=tm,
                ).flush()
                if st.story.id not in stories_with_canonical_tag:
                    StoryTagLog(
                        story=st.story.id,
                        tag=canonical_tag,
                        tag_name=canonical_tag.name,
                        action_flag=StoryTagLog.ADDITION,
                        modified_by=user,
                        date=tm,
                    ).flush()
                    st.tag = canonical_tag
                    st.flush()
                    canonical_tag.stories_count += 1
                    if st.story.published:
                        canonical_tag.published_stories_count += 1
                else:
                    st.delete()
                later(current_app.tasks['sphinx_update_story'].delay, st.story.id, ('tag',))
                tag.stories_count -= 1
                if st.story.published:
                    tag.published_stories_count -= 1

        else:
            tag.is_alias_for = None
            tag.is_hidden_alias = False

        tag.updated_at = tm

        if tag.is_alias_for:
            log_message = 'Тег стал синонимом тега «{}».'.format(tag.is_alias_for.name)
        else:
            log_message = 'Тег перестал быть синонимом.'
        AdminLog.bl.create(
            user=user,
            obj=tag,
            action=AdminLog.CHANGE,
            change_message=log_message,
        )
Ejemplo n.º 21
0
    def update_email_with_confirmation(self, email):
        from mini_fiction.models import Author, ChangeEmailProfile

        user = self.model

        cep = ChangeEmailProfile.get(user=user)
        cep_expired = not cep or cep.date + timedelta(
            days=current_app.config['ACCOUNT_ACTIVATION_DAYS']
        ) < datetime.utcnow()

        if email.lower() == user.email.lower():
            if cep:
                # Отмена установки новой почты
                cep.delete()
            return False

        if Author.bl.is_email_busy(email):
            raise ValidationError(
                {'email': [lazy_gettext('Email address is already in use')]})

        data = {
            'date': datetime.utcnow(),
            'activation_key': utils_random.random_string(40),
            'user': user,
            'email': email,
        }

        if not cep:
            cep = ChangeEmailProfile(**data)
        elif cep_expired or email.lower() != cep.email.lower():
            for k, v in data.items():
                setattr(cep, k, v)

        later(
            current_app.tasks['sendmail'].delay,
            cep.email,
            render_template('email/change_email_subject.txt'),
            body={
                'plain':
                render_template('email/change_email.txt',
                                activation_key=cep.activation_key,
                                user=user),
                'html':
                render_template('email/change_email.html',
                                activation_key=cep.activation_key,
                                user=user),
            },
            headers={
                'X-Postmaster-Msgtype':
                current_app.config['EMAIL_MSGTYPES']['change_email']
            },
        )

        if user.email:
            later(
                current_app.tasks['sendmail'].delay,
                user.email,
                render_template('email/change_email_warning_subject.txt'),
                body={
                    'plain':
                    render_template('email/change_email_warning.txt',
                                    user=user,
                                    new_email=email),
                    'html':
                    render_template('email/change_email_warning.html',
                                    user=user,
                                    new_email=email),
                },
                headers={
                    'X-Postmaster-Msgtype':
                    current_app.config['EMAIL_MSGTYPES']
                    ['change_email_warning']
                },
            )

        return True