Beispiel #1
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        webhook_endpoint_url = build_server_url(local_site_reverse(
            'bitbucket-hooks-close-submitted',
            local_site=repository.local_site,
            kwargs={
                'repository_id': repository.pk,
                'hosting_service_id': repository.hosting_account.service_name,
                'hooks_uuid': repository.get_or_create_hooks_uuid(),
            }))
        add_webhook_url = (
            'https://bitbucket.org/%s/%s/admin/hooks?service=POST&url=%s'
            % (self._get_repository_owner(repository),
               self._get_repository_name(repository),
               webhook_endpoint_url))

        example_id = 123
        example_url = build_server_url(local_site_reverse(
            'review-request-detail',
            local_site=repository.local_site,
            kwargs={
                'review_request_id': example_id,
            }))

        return render_to_string(
            'hostingsvcs/bitbucket/repo_hook_instructions.html',
            RequestContext(request, {
                'example_id': example_id,
                'example_url': example_url,
                'repository': repository,
                'server_url': get_server_url(),
                'add_webhook_url': add_webhook_url,
            }))
def _send_message_from_review(extension, review):
    rr = review.review_request
    repository = rr.repository

    target_people = [u.username for u in rr.target_people.all()]
    participants = set(p.username for p in rr.participants)

    msg = base.GenericMessage()
    msg.routing_parts.append('mozreview.review.published')

    # Try to limit this to data that:
    # * doesn't have an unbound size (like commit messages or diffs)
    # * is useful for consumers to perform quick screening based on state
    #
    # In general, we prefer consumers call the web API to get additional
    # details.
    msg.data['review_id'] = review.id
    msg.data['review_time'] = int(time.mktime(review.timestamp.utctimetuple()))
    msg.data['review_username'] = review.user.username
    msg.data['review_request_id'] = rr.id
    msg.data['review_request_bugs'] = rr.get_bug_list()
    msg.data['review_request_participants'] = sorted(participants)
    msg.data['review_request_submitter'] = rr.submitter.username
    msg.data['review_request_target_people'] = sorted(target_people)
    msg.data['repository_id'] = repository.id
    msg.data['repository_bugtracker_url'] = repository.bug_tracker
    msg.data['repository_url'] = repository.path
    # TODO consider adding participants for this specific thing (e.g. if this
    # is a reply should participants for the review being replied to).

    # TODO make work with RB localsites.
    msg.data['review_board_url'] = get_server_url()

    publish_message(extension, msg)
Beispiel #3
0
 def send_refresh_tools(self):
     """Request workers to update tool list."""
     payload = {
         'session': self.login_user(),
         'url': get_server_url(),
     }
     self.celery.control.broadcast('update_tools_list', payload=payload)
Beispiel #4
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        webhook_endpoint_url = build_server_url(local_site_reverse(
            'bitbucket-hooks-close-submitted',
            local_site=repository.local_site,
            kwargs={
                'repository_id': repository.pk,
                'hosting_service_id': repository.hosting_account.service_name,
                'hooks_uuid': repository.get_or_create_hooks_uuid(),
            }))
        add_webhook_url = (
            'https://bitbucket.org/%s/%s/admin/hooks?service=POST&url=%s'
            % (self._get_repository_owner(repository),
               self._get_repository_name(repository),
               webhook_endpoint_url))

        example_id = 123
        example_url = build_server_url(local_site_reverse(
            'review-request-detail',
            local_site=repository.local_site,
            kwargs={
                'review_request_id': example_id,
            }))

        return render_to_string(
            'hostingsvcs/bitbucket/repo_hook_instructions.html',
            RequestContext(request, {
                'example_id': example_id,
                'example_url': example_url,
                'repository': repository,
                'server_url': get_server_url(),
                'add_webhook_url': add_webhook_url,
            }))
Beispiel #5
0
def preview_password_changed_email(
        request,
        text_template_name='notifications/password_changed.txt',
        html_template_name='notifications/password_changed.html'):
    if not settings.DEBUG:
        raise Http404

    format = request.GET.get('format', 'html')

    if format == 'text':
        template_name = text_template_name
        mimetype = 'text/plain'
    elif format == 'html':
        template_name = html_template_name
        mimetype = 'text/html'
    else:
        raise Http404

    api_token_url = ('%s#api-tokens' %
                     build_server_url(reverse('user-preferences')))

    return HttpResponse(render_to_string(
        template_name,
        RequestContext(
            request, {
                'api_token_url': api_token_url,
                'has_api_tokens': request.user.webapi_tokens.exists(),
                'server_url': get_server_url(),
                'user': request.user,
            })),
                        content_type=mimetype)
def get_server_info(request=None):
    """Returns server information for use in the API.

    This is used for the root resource and for the deprecated server
    info resource.
    """
    capabilities = _capabilities_defaults.copy()
    capabilities.update(_registered_capabilities)

    return {
        'product': {
            'name': 'Review Board',
            'version': get_version_string(),
            'package_version': get_package_version(),
            'is_release': is_release(),
        },
        'site': {
            'url': get_server_url(request=request),
            'administrators': [
                {
                    'name': name,
                    'email': email,
                }
                for name, email in settings.ADMINS
            ],
            'time_zone': settings.TIME_ZONE,
        },
        'capabilities': capabilities
    }
Beispiel #7
0
def get_server_info(request=None):
    """Return server information for use in the API.

    This is used for the root resource and for the deprecated server
    info resource.

    Args:
        request (django.http.HttpRequest, optional):
            The HTTP request from the client.

    Returns:
        dict:
        A dictionary of information about the server and its capabilities.
    """
    return {
        'product': {
            'name': 'Review Board',
            'version': get_version_string(),
            'package_version': get_package_version(),
            'is_release': is_release(),
        },
        'site': {
            'url': get_server_url(request=request),
            'administrators': [
                {
                    'name': name,
                    'email': email,
                }
                for name, email in settings.ADMINS
            ],
            'time_zone': settings.TIME_ZONE,
        },
        'capabilities': get_capabilities(request=request),
    }
Beispiel #8
0
    def process_post_receive_hook(request, *args, **kwargs):
        """Close review requests as submitted automatically after a push.

        Args:
            request (django.http.HttpRequest):
                The request from the Beanstalk webhook.

        Returns:
            django.http.HttpResponse:
            The HTTP response.
        """
        try:
            server_url = get_server_url(request=request)

            # Check if it's a git or an SVN repository and close accordingly.
            if 'payload' in request.POST:
                payload = json.loads(request.POST['payload'])
                BeanstalkHookViews._close_git_review_requests(payload,
                                                              server_url)
            else:
                payload = json.loads(request.POST['commit'])
                BeanstalkHookViews._close_svn_review_request(payload,
                                                             server_url)
        except KeyError as e:
            logging.error('There is no JSON payload in the POST request.: %s',
                          e)
            return HttpResponse(status=415)
        except ValueError as e:
            logging.error('The payload is not in JSON format: %s', e)
            return HttpResponse(status=415)

        return HttpResponse()
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        webhook_endpoint_url = build_server_url(local_site_reverse(
            'googlecode-hooks-close-submitted',
            local_site=repository.local_site,
            kwargs={
                'repository_id': repository.pk,
                'hosting_service_id': repository.hosting_account.service_name,
                'hooks_uuid': repository.get_or_create_hooks_uuid(),
            }))
        add_webhook_url = (
            'https://code.google.com/p/%s/adminSource'
            % repository.extra_data['googlecode_project_name'])

        example_id = 123
        example_url = build_server_url(local_site_reverse(
            'review-request-detail',
            local_site=repository.local_site,
            kwargs={
                'review_request_id': example_id,
            }))

        return render_to_string(
            'hostingsvcs/googlecode/repo_hook_instructions.html',
            RequestContext(request, {
                'example_id': example_id,
                'example_url': example_url,
                'repository': repository,
                'server_url': get_server_url(),
                'add_webhook_url': add_webhook_url,
                'webhook_endpoint_url': webhook_endpoint_url,
            }))
Beispiel #10
0
def prepare_password_changed_mail(user):
    """Return an e-mail notifying the user that their password changed.

    Args:
        user (django.contrib.auth.models.User):
            The user whose password changed.

    Returns:
        EmailMessage:
        The generated message.
    """
    server_url = get_server_url()

    context = {
        'api_token_url': AuthenticationPage.get_absolute_url(),
        'has_api_tokens': user.webapi_tokens.exists(),
        'server_url': server_url,
        'user': user,
    }

    user_email = build_email_address_for_user(user)
    text_body = render_to_string('notifications/password_changed.txt', context)
    html_body = render_to_string('notifications/password_changed.html',
                                 context)

    return EmailMessage(
        subject='Password changed for user "%s" on %s' % (user.username,
                                                          server_url),
        text_body=text_body,
        html_body=html_body,
        from_email=settings.DEFAULT_FROM_EMAIL,
        sender=settings.DEFAULT_FROM_EMAIL,
        to=(user_email,))
Beispiel #11
0
def post_receive_hook_close_submitted(request, local_site_name=None,
                                      repository_id=None,
                                      hosting_service_id=None,
                                      hooks_uuid=None):
    """Closes review requests as submitted automatically after a push."""
    repository = get_repository_for_hook(repository_id, hosting_service_id,
                                         local_site_name, hooks_uuid)

    if 'payload' not in request.POST:
        return HttpResponseBadRequest('Missing payload')

    try:
        payload = json.loads(request.POST['payload'])
    except ValueError as e:
        logging.error('The payload is not in JSON format: %s', e)
        return HttpResponseBadRequest('Invalid payload format')

    server_url = get_server_url(request=request)
    review_request_id_to_commits = \
        _get_review_request_id_to_commits_map(payload, server_url, repository)

    if review_request_id_to_commits:
        close_all_review_requests(review_request_id_to_commits,
                                  local_site_name, repository,
                                  hosting_service_id)

    return HttpResponse()
Beispiel #12
0
def post_receive_hook_close_submitted(request, local_site_name=None,
                                      repository_id=None,
                                      hosting_service_id=None):
    """Closes review requests as submitted automatically after a push."""
    try:
        payload = json.loads(request.body)
    except KeyError as e:
        logging.error('There is no JSON payload in the POST request: %s', e,
                      exc_info=1)
        return HttpResponse(status=400)
    except ValueError as e:
        logging.error('The payload is not in JSON format: %s', e,
                      exc_info=1)
        return HttpResponse(status=400)

    server_url = get_server_url(request=request)
    review_request_id_to_commits_map = \
        close_review_requests(payload, server_url)

    if review_request_id_to_commits_map:
        close_all_review_requests(review_request_id_to_commits_map,
                                  local_site_name, repository_id,
                                  hosting_service_id)

    return HttpResponse()
Beispiel #13
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        webhook_endpoint_url = build_server_url(
            local_site_reverse('googlecode-hooks-close-submitted',
                               local_site=repository.local_site,
                               kwargs={
                                   'repository_id':
                                   repository.pk,
                                   'hosting_service_id':
                                   repository.hosting_account.service_name,
                                   'hooks_uuid':
                                   repository.get_or_create_hooks_uuid(),
                               }))
        add_webhook_url = ('https://code.google.com/p/%s/adminSource' %
                           repository.extra_data['googlecode_project_name'])

        example_id = 123
        example_url = build_server_url(
            local_site_reverse('review-request-detail',
                               local_site=repository.local_site,
                               kwargs={
                                   'review_request_id': example_id,
                               }))

        return render_to_string(
            'hostingsvcs/googlecode/repo_hook_instructions.html',
            RequestContext(
                request, {
                    'example_id': example_id,
                    'example_url': example_url,
                    'repository': repository,
                    'server_url': get_server_url(),
                    'add_webhook_url': add_webhook_url,
                    'webhook_endpoint_url': webhook_endpoint_url,
                }))
Beispiel #14
0
def post_receive_hook_close_submitted(request,
                                      local_site_name=None,
                                      repository_id=None,
                                      hosting_service_id=None,
                                      hooks_uuid=None):
    """Closes review requests as submitted automatically after a push."""
    repository = get_repository_for_hook(repository_id, hosting_service_id,
                                         local_site_name, hooks_uuid)

    try:
        payload = json.loads(request.body)
    except ValueError as e:
        logging.error('The payload is not in JSON format: %s', e, exc_info=1)
        return HttpResponseBadRequest('Invalid payload format')

    server_url = get_server_url(request=request)
    review_request_id_to_commits_map = \
        close_review_requests(payload, server_url)

    if review_request_id_to_commits_map:
        close_all_review_requests(review_request_id_to_commits_map,
                                  local_site_name, repository,
                                  hosting_service_id)

    return HttpResponse()
Beispiel #15
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        plan = repository.extra_data["repository_plan"]
        add_webhook_url = urljoin(
            self.account.hosting_url or "https://github.com/",
            "%s/%s/settings/hooks/new"
            % (
                self._get_repository_owner_raw(plan, repository.extra_data),
                self._get_repository_name_raw(plan, repository.extra_data),
            ),
        )

        webhook_endpoint_url = build_server_url(
            local_site_reverse(
                "github-hooks-close-submitted",
                local_site=repository.local_site,
                kwargs={"repository_id": repository.pk, "hosting_service_id": repository.hosting_account.service_name},
            )
        )

        return render_to_string(
            "hostingsvcs/github/repo_hook_instructions.html",
            RequestContext(
                request,
                {
                    "repository": repository,
                    "server_url": get_server_url(),
                    "add_webhook_url": add_webhook_url,
                    "webhook_endpoint_url": webhook_endpoint_url,
                    "hook_uuid": repository.get_or_create_hooks_uuid(),
                },
            ),
        )
Beispiel #16
0
def get_server_info(request=None):
    """Returns server information for use in the API.

    This is used for the root resource and for the deprecated server
    info resource.
    """
    capabilities = _capabilities_defaults.copy()
    capabilities.update(_registered_capabilities)

    return {
        'product': {
            'name': 'Review Board',
            'version': get_version_string(),
            'package_version': get_package_version(),
            'is_release': is_release(),
        },
        'site': {
            'url':
            get_server_url(request=request),
            'administrators': [{
                'name': name,
                'email': email,
            } for name, email in settings.ADMINS],
            'time_zone':
            settings.TIME_ZONE,
        },
        'capabilities': capabilities
    }
    def process_post_receive_hook(request, *args, **kwargs):
        """Close review requests as submitted automatically after a push.

        Args:
            request (django.http.HttpRequest):
                The request from the Beanstalk webhook.

        Returns:
            django.http.HttpResponse:
            The HTTP response.
        """
        try:
            server_url = get_server_url(request=request)

            # Check if it's a git or an SVN repository and close accordingly.
            if 'payload' in request.POST:
                payload = json.loads(request.POST['payload'])
                BeanstalkHookViews._close_git_review_requests(payload,
                                                              server_url)
            else:
                payload = json.loads(request.POST['commit'])
                BeanstalkHookViews._close_svn_review_request(payload,
                                                             server_url)
        except KeyError as e:
            logging.error('There is no JSON payload in the POST request.: %s',
                          e)
            return HttpResponse(status=415)
        except ValueError as e:
            logging.error('The payload is not in JSON format: %s', e)
            return HttpResponse(status=415)

        return HttpResponse()
def _send_message_from_review(extension, review):
    rr = review.review_request
    repository = rr.repository

    target_people = [u.username for u in rr.target_people.all()]
    participants = set(p.username for p in rr.participants)

    msg = base.GenericMessage()
    msg.routing_parts.append('mozreview.review.published')

    # Try to limit this to data that:
    # * doesn't have an unbound size (like commit messages or diffs)
    # * is useful for consumers to perform quick screening based on state
    #
    # In general, we prefer consumers call the web API to get additional
    # details.
    msg.data['review_id'] = review.id
    msg.data['review_time'] = int(time.mktime(review.timestamp.utctimetuple()))
    msg.data['review_username'] = review.user.username
    msg.data['review_request_id'] = rr.id
    msg.data['review_request_bugs'] = rr.get_bug_list()
    msg.data['review_request_participants'] = sorted(participants)
    msg.data['review_request_submitter'] = rr.submitter.username
    msg.data['review_request_target_people'] = sorted(target_people)
    msg.data['repository_id'] = repository.id
    msg.data['repository_bugtracker_url'] = repository.bug_tracker
    msg.data['repository_url'] = repository.path
    # TODO consider adding participants for this specific thing (e.g. if this
    # is a reply should participants for the review being replied to).

    # TODO make work with RB localsites.
    msg.data['review_board_url'] = get_server_url()

    publish_message(extension, msg)
Beispiel #19
0
 def send_refresh_tools(self):
     """Request workers to update tool list."""
     payload = {
         'session': self.login_user(),
         'url': get_server_url(),
     }
     self.celery.control.broadcast('update_tools_list', payload=payload)
Beispiel #20
0
def get_server_info(request=None):
    """Return server information for use in the API.

    This is used for the root resource and for the deprecated server
    info resource.

    Args:
        request (django.http.HttpRequest, optional):
            The HTTP request from the client.

    Returns:
        dict:
        A dictionary of information about the server and its capabilities.
    """
    return {
        'product': {
            'name': 'Review Board',
            'version': get_version_string(),
            'package_version': get_package_version(),
            'is_release': is_release(),
        },
        'site': {
            'url':
            get_server_url(request=request),
            'administrators': [{
                'name': name,
                'email': email,
            } for name, email in settings.ADMINS],
            'time_zone':
            settings.TIME_ZONE,
        },
        'capabilities': get_capabilities(request=request),
    }
def prepare_password_changed_mail(user):
    """Return an e-mail notifying the user that their password changed.

    Args:
        user (django.contrib.auth.models.User):
            The user whose password changed.

    Returns:
        EmailMessage:
        The generated message.
    """
    server_url = get_server_url()

    context = {
        'api_token_url': AuthenticationPage.get_absolute_url(),
        'has_api_tokens': user.webapi_tokens.exists(),
        'server_url': server_url,
        'user': user,
    }

    user_email = build_email_address_for_user(user)
    text_body = render_to_string(
        template_name='notifications/password_changed.txt', context=context)
    html_body = render_to_string(
        template_name='notifications/password_changed.html', context=context)

    return EmailMessage(subject='Password changed for user "%s" on %s' %
                        (user.username, server_url),
                        text_body=text_body,
                        html_body=html_body,
                        from_email=settings.DEFAULT_FROM_EMAIL,
                        sender=settings.DEFAULT_FROM_EMAIL,
                        to=(user_email, ))
def prepare_webapi_token_mail(webapi_token, op):
    """Return an e-mail message notifying a user about a WebAPI token change.

    Args:
        webapi_token (reviewboard.notifications.models.WebAPIToken):
            The token that was created, updated, or deleted.

        op (unicode):
            The operation on the token. This is one of:

            * ``'created'``
            * ``'updated'``
            * ``'deleted'``

    Returns:
        EmailMessage:
        The genereated e-mail.
    """
    product_name = settings.PRODUCT_NAME

    if op == 'created':
        subject = 'New %s API token created' % product_name
        template_name = 'notifications/api_token_created'
    elif op == 'updated':
        subject = '%s API token updated' % product_name
        template_name = 'notifications/api_token_updated'
    elif op == 'deleted':
        subject = '%s API token deleted' % product_name
        template_name = 'notifications/api_token_deleted'
    else:
        raise ValueError('Unexpected op "%s" passed to mail_webapi_token.' %
                         op)

    user = webapi_token.user
    user_email = build_email_address_for_user(user)

    context = {
        'api_token': webapi_token,
        'api_tokens_url': AuthenticationPage.get_absolute_url(),
        'partial_token': '%s...' % webapi_token.token[:10],
        'user': user,
        'site_root_url': get_server_url(),
        'PRODUCT_NAME': product_name,
    }

    text_message = render_to_string(template_name='%s.txt' % template_name,
                                    context=context)
    html_message = render_to_string(template_name='%s.html' % template_name,
                                    context=context)

    return EmailMessage(subject=subject,
                        text_body=text_message,
                        html_body=html_message,
                        from_email=settings.DEFAULT_FROM_EMAIL,
                        sender=settings.DEFAULT_FROM_EMAIL,
                        to=[user_email])
Beispiel #23
0
def prepare_webapi_token_mail(webapi_token, op):
    """Return an e-mail message notifying a user about a WebAPI token change.

    Args:
        webapi_token (reviewboard.notifications.models.WebAPIToken):
            The token that was created, updated, or deleted.

        op (unicode):
            The operation on the token. This is one of:

            * ``'created'``
            * ``'updated'``
            * ``'deleted'``

    Returns:
        EmailMessage:
        The genereated e-mail.
    """
    if op == 'created':
        subject = 'New Review Board API token created'
        template_name = 'notifications/api_token_created'
    elif op == 'updated':
        subject = 'Review Board API token updated'
        template_name = 'notifications/api_token_updated'
    elif op == 'deleted':
        subject = 'Review Board API token deleted'
        template_name = 'notifications/api_token_deleted'
    else:
        raise ValueError('Unexpected op "%s" passed to mail_webapi_token.' %
                         op)

    user = webapi_token.user
    user_email = build_email_address_for_user(user)

    context = {
        'api_token':
        webapi_token,
        'api_token_url':
        '%s#api-tokens' % build_server_url(reverse('user-preferences')),
        'partial_token':
        '%s...' % webapi_token.token[:10],
        'user':
        user,
        'site_url':
        get_server_url(),
    }

    text_message = render_to_string('%s.txt' % template_name, context)
    html_message = render_to_string('%s.html' % template_name, context)

    return EmailMessage(subject=subject,
                        text_body=text_message,
                        html_body=html_message,
                        from_email=settings.SERVER_EMAIL,
                        sender=settings.SERVER_EMAIL,
                        to=[user_email])
Beispiel #24
0
    def rbtools_setup(self, request, repository_id):
        repository = get_object_or_404(Repository, pk=repository_id)

        return render_to_response(
            'admin/scmtools/repository/rbtools_setup.html',
            RequestContext(request, {
                'repository': repository,
                'reviewboard_url': get_server_url(
                    local_site=repository.local_site),
            }))
Beispiel #25
0
    def rbtools_setup(self, request, repository_id):
        repository = get_object_or_404(Repository, pk=repository_id)

        return render(
            request=request,
            template_name='admin/scmtools/repository/rbtools_setup.html',
            context={
                'repository': repository,
                'reviewboard_url': get_server_url(
                    local_site=repository.local_site),
            })
Beispiel #26
0
def get_server_info(request=None):
    """Returns server information for use in the API.

    This is used for the root resource and for the deprecated server
    info resource.
    """
    return {
        'product': {
            'name': 'Review Board',
            'version': get_version_string(),
            'package_version': get_package_version(),
            'is_release': is_release(),
        },
        'site': {
            'url': get_server_url(request=request),
            'administrators': [
                {
                    'name': name,
                    'email': email,
                }
                for name, email in settings.ADMINS
            ],
            'time_zone': settings.TIME_ZONE,
        },
        'capabilities': {
            'diffs': {
                'base_commit_ids': True,
                'moved_files': True,
            },
            'review_requests': {
                'commit_ids': True,
            },
            'scmtools': {
                'git': {
                    'empty_files': True,
                },
                'mercurial': {
                    'empty_files': True,
                },
                'perforce': {
                    'moved_files': True,
                    'empty_files': True,
                },
                'svn': {
                    'empty_files': True,
                },
            },
            'text': {
                'markdown': True,
                'per_field_text_types': True,
                'can_include_raw_values': True,
            },
        }
    }
Beispiel #27
0
def prepare_webapi_token_mail(webapi_token, op):
    """Return an e-mail message notifying a user about a WebAPI token change.

    Args:
        webapi_token (reviewboard.notifications.models.WebAPIToken):
            The token that was created, updated, or deleted.

        op (unicode):
            The operation on the token. This is one of:

            * ``'created'``
            * ``'updated'``
            * ``'deleted'``

    Returns:
        EmailMessage:
        The genereated e-mail.
    """
    if op == 'created':
        subject = 'New Review Board API token created'
        template_name = 'notifications/api_token_created'
    elif op == 'updated':
        subject = 'Review Board API token updated'
        template_name = 'notifications/api_token_updated'
    elif op == 'deleted':
        subject = 'Review Board API token deleted'
        template_name = 'notifications/api_token_deleted'
    else:
        raise ValueError('Unexpected op "%s" passed to mail_webapi_token.'
                         % op)

    user = webapi_token.user
    user_email = build_email_address_for_user(user)

    context = {
        'api_token': webapi_token,
        'api_tokens_url': AuthenticationPage.get_absolute_url(),
        'partial_token': '%s...' % webapi_token.token[:10],
        'user': user,
        'site_root_url': get_server_url(),
    }

    text_message = render_to_string('%s.txt' % template_name, context)
    html_message = render_to_string('%s.html' % template_name, context)

    return EmailMessage(
        subject=subject,
        text_body=text_message,
        html_body=html_message,
        from_email=settings.DEFAULT_FROM_EMAIL,
        sender=settings.DEFAULT_FROM_EMAIL,
        to=[user_email])
Beispiel #28
0
    def post_receive_hook_close_submitted(request,
                                          local_site_name=None,
                                          repository_id=None,
                                          hosting_service_id=None,
                                          hooks_uuid=None):
        """Close review requests as submitted automatically after a push.

        Args:
            request (django.http.HttpRequest):
                The request from the Bitbucket webhook.

            local_site_name (unicode, optional):
                The local site name, if available.

            repository_id (int, optional):
                The pk of the repository, if available.

            hosting_service_id (unicode, optional):
                The name of the hosting service.

            hooks_uuid (unicode, optional):
                The UUID of the configured webhook.

        Returns:
            django.http.HttpResponse:
            A response for the request.
        """
        repository = get_repository_for_hook(
            repository_id=repository_id,
            hosting_service_id=hosting_service_id,
            local_site_name=local_site_name,
            hooks_uuid=hooks_uuid)

        try:
            payload = json.loads(request.body)
        except ValueError as e:
            logging.error('The payload is not in JSON format: %s', e)
            return HttpResponseBadRequest('Invalid payload format')

        server_url = get_server_url(request=request)
        review_request_id_to_commits = \
            BitbucketHookViews._get_review_request_id_to_commits_map(
                payload, server_url, repository)

        if review_request_id_to_commits:
            close_all_review_requests(review_request_id_to_commits,
                                      local_site_name, repository,
                                      hosting_service_id)

        return HttpResponse()
Beispiel #29
0
    def get(self, request):
        """Query workers and return their status.

        Args:
            request (django.http.HttpRequest):
                The HTTP request.

        Returns:
            django.http.HttpResponse:
            The response.
        """
        extension = ReviewBotExtension.instance
        response = {}

        if extension.is_configured:
            try:
                payload = {
                    'session': extension.login_user(),
                    'url': get_server_url(),
                }
                reply = extension.celery.control.broadcast('update_tools_list',
                                                           payload=payload,
                                                           reply=True,
                                                           timeout=10)

                response = {
                    'state': 'success',
                    'hosts': [
                        {
                            'hostname': hostname.split('@', 1)[1],
                            'tools': data['tools'],
                        }
                        for item in reply
                        for hostname, data in six.iteritems(item)
                    ],
                }
            except IOError as e:
                response = {
                    'state': 'error',
                    'error': 'Unable to connect to broker: %s.' % e,
                }
        else:
            response = {
                'state': 'error',
                'error': 'Review Bot is not yet configured.',
            }

        return HttpResponse(json.dumps(response),
                            content_type='application/json')
Beispiel #30
0
    def post_receive_hook_close_submitted(request, local_site_name=None,
                                          repository_id=None,
                                          hosting_service_id=None,
                                          hooks_uuid=None):
        """Close review requests as submitted automatically after a push.

        Args:
            request (django.http.HttpRequest):
                The request from the Bitbucket webhook.

            local_site_name (unicode):
                The local site name, if available.

            repository_id (int):
                The pk of the repository, if available.

            hosting_service_id (unicode):
                The name of the hosting service.

            hooks_uuid (unicode):
                The UUID of the configured webhook.

        Returns:
            django.http.HttpResponse:
            A response for the request.
        """
        repository = get_repository_for_hook(repository_id, hosting_service_id,
                                             local_site_name, hooks_uuid)

        if 'payload' not in request.POST:
            return HttpResponseBadRequest('Missing payload')

        try:
            payload = json.loads(request.POST['payload'])
        except ValueError as e:
            logging.error('The payload is not in JSON format: %s', e)
            return HttpResponseBadRequest('Invalid payload format')

        server_url = get_server_url(request=request)
        review_request_id_to_commits = \
            BitbucketHookViews._get_review_request_id_to_commits_map(
                payload, server_url, repository)

        if review_request_id_to_commits:
            close_all_review_requests(review_request_id_to_commits,
                                      local_site_name, repository,
                                      hosting_service_id)

        return HttpResponse()
Beispiel #31
0
def post_receive_hook_close_submitted(request,
                                      local_site_name=None,
                                      repository_id=None,
                                      hosting_service_id=None):
    """Closes review requests as submitted automatically after a push."""
    hook_event = request.META.get('HTTP_X_GITHUB_EVENT')

    if hook_event == 'ping':
        # GitHub is checking that this hook is valid, so accept the request
        # and return.
        return HttpResponse()
    elif hook_event != 'push':
        return HttpResponseBadRequest(
            'Only "ping" and "push" events are supported.')

    repository = get_repository_for_hook(repository_id, hosting_service_id,
                                         local_site_name)

    # Validate the hook against the stored UUID.
    m = hmac.new(bytes(repository.get_or_create_hooks_uuid()), request.body,
                 hashlib.sha1)

    sig_parts = request.META.get('HTTP_X_HUB_SIGNATURE').split('=')

    if sig_parts[0] != 'sha1' or len(sig_parts) != 2:
        # We don't know what this is.
        return HttpResponseBadRequest('Unsupported HTTP_X_HUB_SIGNATURE')

    if m.hexdigest() != sig_parts[1]:
        return HttpResponseBadRequest('Bad signature.')

    try:
        payload = json.loads(request.body)
    except ValueError as e:
        logging.error('The payload is not in JSON format: %s', e)
        return HttpResponseBadRequest('Invalid payload format')

    server_url = get_server_url(request=request)
    review_request_id_to_commits = \
        _get_review_request_id_to_commits_map(payload, server_url)

    if review_request_id_to_commits:
        close_all_review_requests(review_request_id_to_commits,
                                  local_site_name, repository,
                                  hosting_service_id)

    return HttpResponse()
Beispiel #32
0
def post_receive_hook_close_submitted(request, local_site_name=None,
                                      repository_id=None,
                                      hosting_service_id=None):
    """Closes review requests as submitted automatically after a push."""
    hook_event = request.META.get('HTTP_X_GITHUB_EVENT')

    if hook_event == 'ping':
        # GitHub is checking that this hook is valid, so accept the request
        # and return.
        return HttpResponse()
    elif hook_event != 'push':
        return HttpResponseBadRequest(
            'Only "ping" and "push" events are supported.')

    repository = get_repository_for_hook(repository_id, hosting_service_id,
                                         local_site_name)

    # Validate the hook against the stored UUID.
    m = hmac.new(bytes(repository.get_or_create_hooks_uuid()), request.body,
                 hashlib.sha1)

    sig_parts = request.META.get('HTTP_X_HUB_SIGNATURE').split('=')

    if sig_parts[0] != 'sha1' or len(sig_parts) != 2:
        # We don't know what this is.
        return HttpResponseBadRequest('Unsupported HTTP_X_HUB_SIGNATURE')

    if m.hexdigest() != sig_parts[1]:
        return HttpResponseBadRequest('Bad signature.')

    try:
        payload = json.loads(request.body)
    except ValueError as e:
        logging.error('The payload is not in JSON format: %s', e)
        return HttpResponseBadRequest('Invalid payload format')

    server_url = get_server_url(request=request)
    review_request_id_to_commits = \
        _get_review_request_id_to_commits_map(payload, server_url, repository)

    if review_request_id_to_commits:
        close_all_review_requests(review_request_id_to_commits,
                                  local_site_name, repository,
                                  hosting_service_id)

    return HttpResponse()
Beispiel #33
0
    def compare_item(self, item_rsp, obj):
        self.assertIn('product', item_rsp)
        self.assertIn('site', item_rsp)
        self.assertIn('capabilities', item_rsp)

        product_rsp = item_rsp['product']
        self.assertEqual(product_rsp['name'], 'Review Board')
        self.assertEqual(product_rsp['version'], get_version_string())
        self.assertEqual(product_rsp['package_version'], get_package_version())
        self.assertEqual(product_rsp['is_release'], is_release())

        site_rsp = item_rsp['site']
        self.assertTrue(site_rsp['url'].startswith(get_server_url()))
        self.assertEqual(site_rsp['administrators'], [{
            'name': name,
            'email': email,
        } for name, email in settings.ADMINS])
        self.assertEqual(site_rsp['time_zone'], settings.TIME_ZONE)

        self.assertEqual(item_rsp['capabilities'], get_capabilities())
Beispiel #34
0
    def on_published(self, review_request=None, changedesc=None, **kwargs):
        if not self.settings['enabled'] or not self.settings['token'] or not self.settings['room']:
            return

        local_site = review_request.local_site
        base_url = get_server_url(local_site=local_site)
        url = urljoin(base_url, review_request.get_absolute_url())
        message = '{author}: <a href="{url}">{title}</a>'.format(
            author=review_request.submitter.username,
            title=review_request.summary,
            url=url)

        hipster = hipchat.HipChat(token=self.settings['token'])

        hipster.method('rooms/message', method='POST', parameters={
            'room_id': int(self.settings['room']),
            'from': self.settings['user'],
            'message': message,
            'message_format': 'html',
            'color': 'purple',
        })
Beispiel #35
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        plan = repository.extra_data['repository_plan']
        add_webhook_url = urljoin(
            self.account.hosting_url or 'https://github.com/',
            '%s/%s/settings/hooks/new' %
            (self._get_repository_owner_raw(plan, repository.extra_data),
             self._get_repository_name_raw(plan, repository.extra_data)))

        webhook_endpoint_url = build_server_url(
            local_site_reverse('github-hooks-close-submitted',
                               local_site=repository.local_site,
                               kwargs={
                                   'repository_id':
                                   repository.pk,
                                   'hosting_service_id':
                                   repository.hosting_account.service_name,
                               }))

        example_id = 123
        example_url = build_server_url(
            local_site_reverse('review-request-detail',
                               local_site=repository.local_site,
                               kwargs={
                                   'review_request_id': example_id,
                               }))

        return render_to_string(
            'hostingsvcs/github/repo_hook_instructions.html',
            RequestContext(
                request, {
                    'example_id': example_id,
                    'example_url': example_url,
                    'repository': repository,
                    'server_url': get_server_url(),
                    'add_webhook_url': add_webhook_url,
                    'webhook_endpoint_url': webhook_endpoint_url,
                    'hook_uuid': repository.get_or_create_hooks_uuid(),
                }))
Beispiel #36
0
    def compare_item(self, item_rsp, obj):
        self.assertIn('product', item_rsp)
        self.assertIn('site', item_rsp)
        self.assertIn('capabilities', item_rsp)

        product_rsp = item_rsp['product']
        self.assertEqual(product_rsp['name'], 'Review Board')
        self.assertEqual(product_rsp['version'], get_version_string())
        self.assertEqual(product_rsp['package_version'], get_package_version())
        self.assertEqual(product_rsp['is_release'], is_release())

        site_rsp = item_rsp['site']
        self.assertTrue(site_rsp['url'].startswith(get_server_url()))
        self.assertEqual(site_rsp['administrators'], [
            {
                'name': name,
                'email': email,
            }
            for name, email in settings.ADMINS
        ])
        self.assertEqual(site_rsp['time_zone'], settings.TIME_ZONE)

        self.assertEqual(item_rsp['capabilities'], get_capabilities())
Beispiel #37
0
def mail_password_changed(user):
    """Send an e-mail when a user's password changes.

    Args:
        user (django.contrib.auth.model.User):
            The user whose password changed.
    """
    api_token_url = ('%s#api-tokens' %
                     build_server_url(reverse('user-preferences')))
    server_url = get_server_url()

    context = {
        'api_token_url': api_token_url,
        'has_api_tokens': user.webapi_tokens.exists(),
        'server_url': server_url,
        'user': user,
    }

    user_email = build_email_address_for_user(user)
    text_body = render_to_string('notifications/password_changed.txt', context)
    html_body = render_to_string('notifications/password_changed.html',
                                 context)

    message = EmailMessage(
        subject='Password changed for user "%s" on %s' % server_url,
        text_body=text_body,
        html_body=html_body,
        from_email=settings.SERVER_EMAIL,
        sender=settings.SERVER_EMAIL,
        to=user_email,
    )

    try:
        message.send()
    except Exception as e:
        logging.exception('Failed to send password changed email to %s: %s',
                          user.username, e)
Beispiel #38
0
def prepare_user_registered_mail(user):
    """Prepare an e-mail to the administrators notifying of a new user.

    Args:
        user (django.contrib.auth.models.User):
            The user who registered.

    Returns:
        EmailMessage:
        The generated e-mail.
    """
    subject = "New Review Board user registration for %s" % user.username

    context = {
        'site_url':
        get_server_url(),
        'user':
        user,
        'user_url':
        build_server_url(reverse('admin:auth_user_change', args=(user.id, ))),
    }

    text_message = render_to_string('notifications/new_user_email.txt',
                                    context)
    html_message = render_to_string('notifications/new_user_email.html',
                                    context)

    return EmailMessage(subject=subject.strip(),
                        text_body=text_message,
                        html_body=html_message,
                        from_email=settings.SERVER_EMAIL,
                        sender=settings.SERVER_EMAIL,
                        to=[
                            build_email_address(full_name=admin[0],
                                                email=admin[1])
                            for admin in settings.ADMINS
                        ])
Beispiel #39
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        plan = repository.extra_data['repository_plan']
        add_webhook_url = urljoin(
            self.account.hosting_url or 'https://github.com/',
            '%s/%s/settings/hooks/new'
            % (self._get_repository_owner_raw(plan, repository.extra_data),
               self._get_repository_name_raw(plan, repository.extra_data)))

        webhook_endpoint_url = build_server_url(local_site_reverse(
            'github-hooks-close-submitted',
            local_site=repository.local_site,
            kwargs={
                'repository_id': repository.pk,
                'hosting_service_id': repository.hosting_account.service_name,
            }))

        example_id = 123
        example_url = build_server_url(local_site_reverse(
            'review-request-detail',
            local_site=repository.local_site,
            kwargs={
                'review_request_id': example_id,
            }))

        return render_to_string(
            'hostingsvcs/github/repo_hook_instructions.html',
            RequestContext(request, {
                'example_id': example_id,
                'example_url': example_url,
                'repository': repository,
                'server_url': get_server_url(),
                'add_webhook_url': add_webhook_url,
                'webhook_endpoint_url': webhook_endpoint_url,
                'hook_uuid': repository.get_or_create_hooks_uuid(),
            }))
Beispiel #40
0
def prepare_password_changed_mail(user):
    """Return an e-mail notifying the user that their password changed.

    Args:
        user (django.contrib.auth.models.User):
            The user whose password changed.

    Returns:
        EmailMessage:
        The generated message.
    """
    server_url = get_server_url()

    context = {
        'api_token_url':
        '%s#api-tokens' % build_server_url(reverse('user-preferences')),
        'has_api_tokens':
        user.webapi_tokens.exists(),
        'server_url':
        server_url,
        'user':
        user,
    }

    user_email = build_email_address_for_user(user)
    text_body = render_to_string('notifications/password_changed.txt', context)
    html_body = render_to_string('notifications/password_changed.html',
                                 context)

    return EmailMessage(subject='Password changed for user "%s" on %s' %
                        (user.username, server_url),
                        text_body=text_body,
                        html_body=html_body,
                        from_email=settings.SERVER_EMAIL,
                        sender=settings.SERVER_EMAIL,
                        to=user_email)
Beispiel #41
0
def prepare_base_review_request_mail(user,
                                     review_request,
                                     subject,
                                     in_reply_to,
                                     to_field,
                                     cc_field,
                                     template_name_base,
                                     context=None,
                                     extra_headers=None):
    """Return a customized review request e-mail.

    This is intended to be called by one of the ``prepare_{type}_mail``
    functions in this file. This method builds up a common context that all
    review request-related e-mails will use to render their templates, as well
    as handling user preferences regarding e-mail and add adding additional
    headers.

    Args:
        user (django.contrib.auth.models.User):
            The user who is sending the e-mail.

        review_request (reviewboard.reviews.models.review_request.ReviewRequest):
            The review request this e-mail is regarding.

        subject (unicode):
            The e-mail subject line.

        in_reply_to (unicode):
            The e-mail message ID this message is in response to or ``None``.

        to_field (set):
            The set of :py:class:`~django.contrib.auth.models.User` and
            :py:class`~reviewboard.reviews.models.group.Group`s to this e-mail
            will be sent to.

        cc_field (set):
            The set of :py:class:`~django.contrib.auth.models.User` and
            :py:class`~reviewboard.reviews.models.group.Group`s to be CC'ed on
            the e-mail.

        template_name_base (unicode):
            The name of the template to use to generate the e-mail without its
            extension. The plain-text version of the e-mail will append
            ``.txt`` to this and and the rich-text version of the e-mail will
            append ``.html``.

        context (dict, optional):
            Optional additional template rendering context.

        extra_headers (dict, optional):
            Optional additional headers to include.

    Returns:
        EmailMessage:
        The prepared e-mail message.
    """
    user_email = build_email_address_for_user(user)
    to_field = recipients_to_addresses(to_field, review_request.id)
    cc_field = recipients_to_addresses(cc_field, review_request.id) - to_field

    if not user.should_send_own_updates():
        to_field.discard(user_email)
        cc_field.discard(user_email)

    if not to_field and not cc_field:
        # This e-mail would have no recipients, so we won't send it.
        return None

    if not context:
        context = {}

    context.update({
        'user': user,
        'site_url': get_server_url(),
        'review_request': review_request,
    })
    local_site = review_request.local_site

    if local_site:
        context['local_site_name'] = local_site.name

    text_body = render_to_string('%s.txt' % template_name_base, context)
    html_body = render_to_string('%s.html' % template_name_base, context)
    server_url = get_server_url(local_site=local_site)

    headers = MultiValueDict({
        'X-ReviewBoard-URL': [server_url],
        'X-ReviewRequest-URL': [
            build_server_url(review_request.get_absolute_url(),
                             local_site=local_site)
        ],
        'X-ReviewGroup': [
            ', '.join(
                review_request.target_groups.values_list('name', flat=True))
        ],
    })

    if extra_headers:
        if not isinstance(extra_headers, MultiValueDict):
            extra_headers = MultiValueDict(
                (key, [value]) for key, value in six.iteritems(extra_headers))

        headers.update(extra_headers)

    if review_request.repository:
        headers['X-ReviewRequest-Repository'] = review_request.repository.name

    latest_diffset = review_request.get_latest_diffset()

    if latest_diffset:
        modified_files = set()

        for filediff in latest_diffset.files.all():
            if filediff.deleted or filediff.copied or filediff.moved:
                modified_files.add(filediff.source_file)

            if filediff.is_new or filediff.copied or filediff.moved:
                modified_files.add(filediff.dest_file)

        # The following code segment deals with the case where the client adds
        # a significant amount of files with large names. We limit the number
        # of headers; when more than 8192 characters are reached, we stop
        # adding filename headers.
        current_header_length = 0

        for filename in modified_files:
            current_header_length += (HEADER_ADDITIONAL_CHARACTERS_LENGTH +
                                      len(filename))

            if current_header_length > MAX_FILENAME_HEADERS_LENGTH:
                logging.warning(
                    'Unable to store all filenames in the '
                    'X-ReviewBoard-Diff-For headers when sending e-mail for '
                    'review request %s: The header size exceeds the limit of '
                    '%s. Remaining headers have been omitted.',
                    review_request.display_id, MAX_FILENAME_HEADERS_LENGTH)
                break

            headers.appendlist('X-ReviewBoard-Diff-For', filename)

    if settings.DEFAULT_FROM_EMAIL:
        sender = build_email_address(full_name=user.get_full_name(),
                                     email=settings.DEFAULT_FROM_EMAIL)
    else:
        sender = None

    return EmailMessage(subject=subject.strip(),
                        text_body=text_body.encode('utf-8'),
                        html_body=html_body.encode('utf-8'),
                        from_email=user_email,
                        sender=sender,
                        to=list(to_field),
                        cc=list(cc_field),
                        in_reply_to=in_reply_to,
                        headers=headers)
Beispiel #42
0
def send_review_mail(user, review_request, subject, in_reply_to,
                     extra_recipients, text_template_name,
                     html_template_name, context={}):
    """
    Formats and sends an e-mail out with the current domain and review request
    being added to the template context. Returns the resulting message ID.
    """
    current_site = Site.objects.get_current()
    local_site = review_request.local_site
    from_email = get_email_address_for_user(user)
    target_people = review_request.target_people.filter(is_active=True)
    recipients = set()
    to_field = set()

    if local_site:
        # Filter out users who are on the reviewer list in some form, but
        # no longer part of the LocalSite.
        local_site_q = (Q(local_site=local_site) |
                        Q(local_site_admins=local_site))

        target_people = target_people.filter(local_site_q)

        if extra_recipients and local_site:
            extra_recipients = User.objects.filter(
                Q(username__in=extra_recipients) & local_site_q)

    if from_email and user.should_send_email():
        recipients.add(from_email)

    if (review_request.submitter.is_active and
        review_request.submitter.should_send_email()):
        recipients.add(get_email_address_for_user(review_request.submitter))

    for group in review_request.target_groups.all():
        for address in get_email_addresses_for_group(group):
            recipients.add(address)

    for profile in review_request.starred_by.all():
        if profile.user.is_active and profile.should_send_email:
            recipients.add(get_email_address_for_user(profile.user))

    if extra_recipients:
        for recipient in extra_recipients:
            if recipient.is_active and recipient.should_send_email():
                recipients.add(get_email_address_for_user(recipient))

    for u in target_people:
        if u.should_send_email():
            email_address = get_email_address_for_user(u)
            recipients.add(email_address)
            to_field.add(email_address)

    if not user.should_send_own_updates():
        recipients.discard(from_email)
        to_field.discard(from_email)

    siteconfig = current_site.config.get()
    domain_method = siteconfig.get("site_domain_method")

    context['user'] = user
    context['domain'] = current_site.domain
    context['domain_method'] = domain_method
    context['review_request'] = review_request

    if review_request.local_site:
        context['local_site_name'] = review_request.local_site.name

    text_body = render_to_string(text_template_name, context)
    html_body = render_to_string(html_template_name, context)

    # Set the cc field only when the to field (i.e People) are mentioned,
    # so that to field consists of Reviewers and cc consists of all the
    # other members of the group
    if to_field:
        cc_field = recipients.symmetric_difference(to_field)
    else:
        to_field = recipients
        cc_field = set()

    base_url = get_server_url(local_site=local_site)

    headers = {
        'X-ReviewBoard-URL': base_url,
        'X-ReviewRequest-URL': urljoin(base_url,
                                       review_request.get_absolute_url()),
        'X-ReviewGroup': ', '.join(group.name for group in
                                   review_request.target_groups.all()),
    }

    if review_request.repository:
        headers['X-ReviewRequest-Repository'] = review_request.repository.name

    sender = None

    if settings.DEFAULT_FROM_EMAIL:
        sender = build_email_address(user.get_full_name(),
                                     settings.DEFAULT_FROM_EMAIL)

        if sender == from_email:
            # RFC 2822 states that we should only include Sender if the
            # two are not equal.
            sender = None

    message = SpiffyEmailMessage(subject.strip(), text_body, html_body,
                                 from_email, sender, list(to_field),
                                 list(cc_field), in_reply_to, headers)
    try:
        message.send()
    except Exception as e:
        logging.error("Error sending e-mail notification with subject '%s' on "
                      "behalf of '%s' to '%s': %s",
                      subject.strip(),
                      from_email,
                      ','.join(list(to_field) + list(cc_field)),
                      e,
                      exc_info=1)

    return message.message_id
Beispiel #43
0
def send_review_mail(user,
                     review_request,
                     subject,
                     in_reply_to,
                     extra_recipients,
                     text_template_name,
                     html_template_name,
                     context={}):
    """
    Formats and sends an e-mail out with the current domain and review request
    being added to the template context. Returns the resulting message ID.
    """
    current_site = Site.objects.get_current()
    local_site = review_request.local_site
    from_email = get_email_address_for_user(user)
    target_people = review_request.target_people.filter(is_active=True)
    recipients = set()
    to_field = set()

    if local_site:
        # Filter out users who are on the reviewer list in some form, but
        # no longer part of the LocalSite.
        local_site_q = (Q(local_site=local_site)
                        | Q(local_site_admins=local_site))

        target_people = target_people.filter(local_site_q)

        if extra_recipients and local_site:
            extra_recipients = User.objects.filter(
                Q(username__in=extra_recipients) & local_site_q)

    if from_email and user.should_send_email():
        recipients.add(from_email)

    if (review_request.submitter.is_active
            and review_request.submitter.should_send_email()):
        recipients.add(get_email_address_for_user(review_request.submitter))

    for u in target_people:
        if u.should_send_email():
            email_address = get_email_address_for_user(u)
            recipients.add(email_address)
            to_field.add(email_address)

    for group in review_request.target_groups.all():
        for address in get_email_addresses_for_group(group):
            recipients.add(address)

    for profile in review_request.starred_by.all():
        if profile.user.is_active and profile.should_send_email:
            recipients.add(get_email_address_for_user(profile.user))

    if extra_recipients:
        for recipient in extra_recipients:
            if recipient.is_active and recipient.should_send_email():
                recipients.add(get_email_address_for_user(recipient))

    siteconfig = current_site.config.get()
    domain_method = siteconfig.get("site_domain_method")

    context['user'] = user
    context['domain'] = current_site.domain
    context['domain_method'] = domain_method
    context['review_request'] = review_request

    if review_request.local_site:
        context['local_site_name'] = review_request.local_site.name

    text_body = render_to_string(text_template_name, context)
    html_body = render_to_string(html_template_name, context)

    # Set the cc field only when the to field (i.e People) are mentioned,
    # so that to field consists of Reviewers and cc consists of all the
    # other members of the group
    if to_field:
        cc_field = recipients.symmetric_difference(to_field)
    else:
        to_field = recipients
        cc_field = set()

    base_url = get_server_url(local_site=local_site)

    headers = {
        'X-ReviewBoard-URL':
        base_url,
        'X-ReviewRequest-URL':
        urljoin(base_url, review_request.get_absolute_url()),
        'X-ReviewGroup':
        ', '.join(group.name for group in review_request.target_groups.all()),
    }

    if review_request.repository:
        headers['X-ReviewRequest-Repository'] = review_request.repository.name

    sender = None

    if settings.DEFAULT_FROM_EMAIL:
        sender = build_email_address(user.get_full_name(),
                                     settings.DEFAULT_FROM_EMAIL)

        if sender == from_email:
            # RFC 2822 states that we should only include Sender if the
            # two are not equal.
            sender = None

    message = SpiffyEmailMessage(subject.strip(), text_body, html_body,
                                 from_email, sender, list(to_field),
                                 list(cc_field), in_reply_to, headers)
    try:
        message.send()
    except Exception as e:
        logging.error(
            "Error sending e-mail notification with subject '%s' on "
            "behalf of '%s' to '%s': %s",
            subject.strip(),
            from_email,
            ','.join(list(to_field) + list(cc_field)),
            e,
            exc_info=1)

    return message.message_id
Beispiel #44
0
def send_review_mail(user, review_request, subject, in_reply_to,
                     extra_recipients, text_template_name,
                     html_template_name, context={}, limit_recipients_to=None):
    """
    Formats and sends an e-mail out with the current domain and review request
    being added to the template context. Returns the resulting message ID.
    """
    current_site = Site.objects.get_current()
    local_site = review_request.local_site
    from_email = get_email_address_for_user(user)
    target_people = review_request.target_people.filter(is_active=True)
    recipients = set()
    to_field = set()

    target_people = target_people.extra(select={
        'visibility': """
            SELECT accounts_reviewrequestvisit.visibility
              FROM accounts_reviewrequestvisit
             WHERE accounts_reviewrequestvisit.review_request_id =
                   reviews_reviewrequest_target_people.reviewrequest_id
               AND accounts_reviewrequestvisit.user_id =
                   reviews_reviewrequest_target_people.user_id
        """
    })

    if local_site:
        # Filter out users who are on the reviewer list in some form, but
        # no longer part of the LocalSite.
        local_site_q = (Q(local_site=local_site) |
                        Q(local_site_admins=local_site))

        target_people = target_people.filter(local_site_q)

        if extra_recipients and local_site:
            extra_recipients = User.objects.filter(
                Q(username__in=extra_recipients) & local_site_q)

    if from_email and user.should_send_email():
        recipients.add(from_email)

    if (review_request.submitter.is_active and
        review_request.submitter.should_send_email()):
        recipients.add(get_email_address_for_user(review_request.submitter))

    for profile in review_request.starred_by.all():
        if profile.user.is_active and profile.should_send_email:
            recipients.add(get_email_address_for_user(profile.user))

    if limit_recipients_to is not None:
        recipients.update(limit_recipients_to)
    else:
        if extra_recipients:
            for recipient in extra_recipients:
                if recipient.is_active and recipient.should_send_email():
                    recipients.add(get_email_address_for_user(recipient))

        for group in review_request.target_groups.all():
            recipients.update(get_email_addresses_for_group(
                group, review_request_id=review_request.id))

        for u in target_people:
            if (u.should_send_email() and
                u.visibility != ReviewRequestVisit.MUTED):
                email_address = get_email_address_for_user(u)
                recipients.add(email_address)
                to_field.add(email_address)

    if not user.should_send_own_updates():
        recipients.discard(from_email)
        to_field.discard(from_email)

    if not recipients and not to_field:
        # Nothing to send.
        return

    siteconfig = current_site.config.get()
    domain_method = siteconfig.get("site_domain_method")

    context['user'] = user
    context['domain'] = current_site.domain
    context['domain_method'] = domain_method
    context['review_request'] = review_request

    if review_request.local_site:
        context['local_site_name'] = review_request.local_site.name

    text_body = render_to_string(text_template_name, context)
    html_body = render_to_string(html_template_name, context)

    # Set the cc field only when the to field (i.e People) are mentioned,
    # so that to field consists of Reviewers and cc consists of all the
    # other members of the group.
    if to_field:
        cc_field = recipients.symmetric_difference(to_field)
    else:
        to_field = recipients
        cc_field = set()

    base_url = get_server_url(local_site=local_site)

    headers = MultiValueDict({
        'X-ReviewBoard-URL': [base_url],
        'X-ReviewRequest-URL': [urljoin(base_url,
                                        review_request.get_absolute_url())],
        'X-ReviewGroup': [', '.join(group.name for group in
                                    review_request.target_groups.all())],
    })

    if review_request.repository:
        headers['X-ReviewRequest-Repository'] = review_request.repository.name

    latest_diffset = review_request.get_latest_diffset()

    if latest_diffset:
        modified_files = set()

        for filediff in latest_diffset.files.all():
            if filediff.deleted or filediff.copied or filediff.moved:
                modified_files.add(filediff.source_file)

            if filediff.is_new or filediff.copied or filediff.moved:
                modified_files.add(filediff.dest_file)

        for filename in modified_files:
            headers.appendlist('X-ReviewBoard-Diff-For', filename)

    sender = None

    if settings.DEFAULT_FROM_EMAIL:
        sender = build_email_address(user.get_full_name(),
                                     settings.DEFAULT_FROM_EMAIL)

        if sender == from_email:
            # RFC 2822 states that we should only include Sender if the
            # two are not equal.
            sender = None

    message = SpiffyEmailMessage(subject.strip(), text_body, html_body,
                                 from_email, sender, list(to_field),
                                 list(cc_field), in_reply_to, headers)
    try:
        message.send()
    except Exception:
        logging.exception("Error sending e-mail notification with subject "
                          "'%s' on behalf of '%s' to '%s'",
                          subject.strip(),
                          from_email,
                          ','.join(list(to_field) + list(cc_field)))

    return message.message_id
Beispiel #45
0
    def post_receive_hook_close_submitted(request,
                                          local_site_name=None,
                                          repository_id=None,
                                          hosting_service_id=None):
        """Close review requests as submitted automatically after a push.

        Args:
            request (django.http.HttpRequest):
                The request from the Bitbucket webhook.

            local_site_name (unicode):
                The local site name, if available.

            repository_id (int):
                The pk of the repository, if available.

            hosting_service_id (unicode):
                The name of the hosting service.

        Returns:
            django.http.HttpResponse:
            A response for the request.
        """
        hook_event = request.META.get('HTTP_X_GITHUB_EVENT')

        if hook_event == 'ping':
            # GitHub is checking that this hook is valid, so accept the request
            # and return.
            return HttpResponse()
        elif hook_event != 'push':
            return HttpResponseBadRequest(
                'Only "ping" and "push" events are supported.')

        repository = get_repository_for_hook(repository_id, hosting_service_id,
                                             local_site_name)

        # Validate the hook against the stored UUID.
        m = hmac.new(repository.get_or_create_hooks_uuid().encode('utf-8'),
                     request.body, hashlib.sha1)

        sig_parts = request.META.get('HTTP_X_HUB_SIGNATURE').split('=')

        if sig_parts[0] != 'sha1' or len(sig_parts) != 2:
            # We don't know what this is.
            return HttpResponseBadRequest('Unsupported HTTP_X_HUB_SIGNATURE')

        if m.hexdigest() != sig_parts[1]:
            return HttpResponseBadRequest('Bad signature.')

        try:
            payload = json.loads(request.body.decode('utf-8'))
        except ValueError as e:
            logger.error('The payload is not in JSON format: %s', e)
            return HttpResponseBadRequest('Invalid payload format')

        server_url = get_server_url(request=request)
        review_request_id_to_commits = \
            GitHubHookViews._get_review_request_id_to_commits_map(
                payload, server_url, repository)

        if review_request_id_to_commits:
            close_all_review_requests(review_request_id_to_commits,
                                      local_site_name, repository,
                                      hosting_service_id)

        return HttpResponse()
Beispiel #46
0
    def _on_review_request_published(self,
                                     sender,
                                     review_request,
                                     changedesc=None,
                                     **kwargs):
        """Handle when a review request is published.

        Args:
            sender (object):
                The sender of the signal.

            review_request (reviewboard.reviews.models.review_request.
                            ReviewRequest):
                The review request which was published.

            changedesc (reviewboard.changedescs.models.ChangeDescription,
                        optional):
                The change description associated with this publish.

            **kwargs (dict):
                Additional keyword arguments.
        """
        # Only build changes against GitHub or Bitbucket repositories.
        repository = review_request.repository

        if not repository or not repository.hosting_account:
            return

        service_name = repository.hosting_account.service_name

        if service_name not in ('github', 'bitbucket'):
            return

        diffset = review_request.get_latest_diffset()

        # Don't build any review requests that don't include diffs.
        if not diffset:
            return

        # If this was an update to a review request, make sure that there was a
        # diff update in it.
        if changedesc is not None:
            fields_changed = changedesc.fields_changed

            if ('diff' not in fields_changed
                    or 'added' not in fields_changed['diff']):
                return

        matching_configs = [
            config for config in self.get_configs(review_request.local_site)
            if config.match_conditions(form_cls=self.config_form_cls,
                                       review_request=review_request)
        ]

        if not matching_configs:
            return

        # This may look weird, but it's here for defensive purposes.
        # Currently, the possible values for CircleCI's "vcs-type" field in
        # their API happens to match up perfectly with our service names,
        # but that's not necessarily always going to be the case.
        if service_name == 'github':
            vcs_type = 'github'
        elif service_name == 'bitbucket':
            vcs_type = 'bitbucket'
        else:
            raise ValueError('Unexpected hosting service type got through '
                             'to CircleCI invocation: %s' % service_name)

        org_name, repo_name = self._get_repo_ids(service_name, repository)

        user = self._get_or_create_user()

        for config in matching_configs:
            status_update = StatusUpdate.objects.create(
                service_id='circle-ci',
                user=user,
                summary='CircleCI',
                description='starting build...',
                state=StatusUpdate.PENDING,
                review_request=review_request,
                change_description=changedesc)

            url = ('https://circleci.com/api/v1.1/project/%s/%s/%s/tree/%s'
                   '?circle-token=%s' %
                   (vcs_type, org_name, repo_name, config.get('branch_name') or
                    'master', urlquote_plus(config.get('circle_api_token'))))

            logger.info('Making CircleCI API request: %s', url)

            local_site = config.local_site

            try:
                token = user.webapi_tokens.filter(local_site=local_site)[0]
            except IndexError:
                token = WebAPIToken.objects.generate_token(
                    user, local_site=local_site, autogenerated=True)

            body = {
                'revision': diffset.base_commit_id,
                'build_parameters': {
                    'CIRCLE_JOB':
                    'reviewboard',
                    'REVIEWBOARD_SERVER':
                    get_server_url(local_site=config.local_site),
                    'REVIEWBOARD_REVIEW_REQUEST':
                    review_request.display_id,
                    'REVIEWBOARD_DIFF_REVISION':
                    diffset.revision,
                    'REVIEWBOARD_API_TOKEN':
                    token.token,
                    'REVIEWBOARD_STATUS_UPDATE_ID':
                    status_update.pk,
                },
            }

            if config.local_site:
                body['build_parameters']['REVIEWBOARD_LOCAL_SITE'] = \
                    config.local_site.name

            request = URLRequest(url,
                                 body=json.dumps(body),
                                 headers={
                                     'Accept': 'application/json',
                                     'Content-Type': 'application/json',
                                 },
                                 method='POST')

            u = urlopen(request)

            data = json.loads(u.read())

            status_update.url = data['build_url']
            status_update.url_text = 'View Build'
            status_update.save()
Beispiel #47
0
def prepare_base_review_request_mail(user, review_request, subject,
                                     in_reply_to, to_field, cc_field,
                                     template_name_base, context=None,
                                     extra_headers=None):
    """Return a customized review request e-mail.

    This is intended to be called by one of the ``prepare_{type}_mail``
    functions in this file. This method builds up a common context that all
    review request-related e-mails will use to render their templates, as well
    as handling user preferences regarding e-mail and add adding additional
    headers.

    Args:
        user (django.contrib.auth.models.User):
            The user who is sending the e-mail.

        review_request (reviewboard.reviews.models.review_request.ReviewRequest):
            The review request this e-mail is regarding.

        subject (unicode):
            The e-mail subject line.

        in_reply_to (unicode):
            The e-mail message ID this message is in response to or ``None``.

        to_field (set):
            The set of :py:class:`~django.contrib.auth.models.User` and
            :py:class`~reviewboard.reviews.models.group.Group`s to this e-mail
            will be sent to.

        cc_field (set):
            The set of :py:class:`~django.contrib.auth.models.User` and
            :py:class`~reviewboard.reviews.models.group.Group`s to be CC'ed on
            the e-mail.

        template_name_base (unicode):
            The name of the template to use to generate the e-mail without its
            extension. The plain-text version of the e-mail will append
            ``.txt`` to this and and the rich-text version of the e-mail will
            append ``.html``.

        context (dict, optional):
            Optional additional template rendering context.

        extra_headers (dict, optional):
            Optional additional headers to include.

    Returns:
        EmailMessage:
        The prepared e-mail message.
    """
    user_email = build_email_address_for_user(user)
    to_field = recipients_to_addresses(to_field, review_request.id)
    cc_field = recipients_to_addresses(cc_field, review_request.id) - to_field

    if not user.should_send_own_updates():
        to_field.discard(user_email)
        cc_field.discard(user_email)

    if not to_field and not cc_field:
        # This e-mail would have no recipients, so we won't send it.
        return None

    if not context:
        context = {}

    context.update({
        'user': user,
        'site_url': _get_server_base_url(),
        'review_request': review_request,
    })
    local_site = review_request.local_site

    if local_site:
        context['local_site_name'] = local_site.name

    text_body = render_to_string('%s.txt' % template_name_base, context)
    html_body = render_to_string('%s.html' % template_name_base, context)
    server_url = get_server_url(local_site=local_site)

    headers = MultiValueDict({
        'X-ReviewBoard-URL': [server_url],
        'X-ReviewRequest-URL': [
            build_server_url(review_request.get_absolute_url(),
                             local_site=local_site)
        ],
        'X-ReviewGroup': [', '.join(
            review_request.target_groups.values_list('name', flat=True)
        )],
    })

    if extra_headers:
        if not isinstance(extra_headers, MultiValueDict):
            extra_headers = MultiValueDict(
                (key, [value])
                for key, value in six.iteritems(extra_headers)
            )

        headers.update(extra_headers)

    if review_request.repository:
        headers['X-ReviewRequest-Repository'] = review_request.repository.name

    latest_diffset = review_request.get_latest_diffset()

    if latest_diffset:
        modified_files = set()

        for filediff in latest_diffset.files.all():
            if not filediff.is_new:
                modified_files.add(filediff.source_file)

            if not filediff.deleted:
                modified_files.add(filediff.dest_file)

        # The following code segment deals with the case where the client adds
        # a significant amount of files with large names. We limit the number
        # of headers; when more than 8192 characters are reached, we stop
        # adding filename headers.
        current_header_length = 0

        for filename in modified_files:
            current_header_length += (HEADER_ADDITIONAL_CHARACTERS_LENGTH +
                                      len(filename))

            if current_header_length > MAX_FILENAME_HEADERS_LENGTH:
                logging.warning(
                    'Unable to store all filenames in the '
                    'X-ReviewBoard-Diff-For headers when sending e-mail for '
                    'review request %s: The header size exceeds the limit of '
                    '%s. Remaining headers have been omitted.',
                    review_request.display_id,
                    MAX_FILENAME_HEADERS_LENGTH)
                break

            headers.appendlist('X-ReviewBoard-Diff-For', filename)

    if settings.DEFAULT_FROM_EMAIL:
        sender = build_email_address(full_name=user.get_full_name(),
                                     email=settings.DEFAULT_FROM_EMAIL)
    else:
        sender = None

    return EmailMessage(subject=subject.strip(),
                        text_body=text_body.encode('utf-8'),
                        html_body=html_body.encode('utf-8'),
                        from_email=user_email,
                        sender=sender,
                        to=list(to_field),
                        cc=list(cc_field),
                        in_reply_to=in_reply_to,
                        headers=headers)
    def _on_review_request_published(self, sender, review_request,
                                     changedesc=None, **kwargs):
        """Handle when a review request is published.

        Args:
            sender (object):
                The sender of the signal.

            review_request (reviewboard.reviews.models.review_request.
                            ReviewRequest):
                The review request which was published.

            changedesc (reviewboard.changedescs.models.ChangeDescription,
                        optional):
                The change description associated with this publish.

            **kwargs (dict):
                Additional keyword arguments.
        """
        # Only build changes against GitHub or Bitbucket repositories.
        repository = review_request.repository

        if not repository or not repository.hosting_account:
            return

        service_name = repository.hosting_account.service_name

        if service_name not in ('github', 'bitbucket'):
            return

        diffset = review_request.get_latest_diffset()

        # Don't build any review requests that don't include diffs.
        if not diffset:
            return

        # If this was an update to a review request, make sure that there was a
        # diff update in it.
        if changedesc is not None:
            fields_changed = changedesc.fields_changed

            if ('diff' not in fields_changed or
                'added' not in fields_changed['diff']):
                return

        matching_configs = [
            config
            for config in self.get_configs(review_request.local_site)
            if config.match_conditions(form_cls=self.config_form_cls,
                                       review_request=review_request)
        ]

        if not matching_configs:
            return

        # This may look weird, but it's here for defensive purposes.
        # Currently, the possible values for CircleCI's "vcs-type" field in
        # their API happens to match up perfectly with our service names,
        # but that's not necessarily always going to be the case.
        if service_name == 'github':
            vcs_type = 'github'
        elif service_name == 'bitbucket':
            vcs_type = 'bitbucket'
        else:
            raise ValueError('Unexpected hosting service type got through '
                             'to CircleCI invocation: %s'
                             % service_name)

        org_name, repo_name = self._get_repo_ids(service_name, repository)

        user = self._get_or_create_user()

        for config in matching_configs:
            status_update = StatusUpdate.objects.create(
                service_id='circle-ci',
                user=user,
                summary='CircleCI',
                description='starting build...',
                state=StatusUpdate.PENDING,
                review_request=review_request,
                change_description=changedesc)

            url = ('https://circleci.com/api/v1.1/project/%s/%s/%s/tree/%s'
                   '?circle-token=%s'
                   % (vcs_type, org_name, repo_name,
                      config.get('branch_name') or 'master',
                      urlquote_plus(config.get('circle_api_token'))))

            logger.info('Making CircleCI API request: %s', url)

            local_site = config.local_site

            try:
                token = user.webapi_tokens.filter(local_site=local_site)[0]
            except IndexError:
                token = WebAPIToken.objects.generate_token(
                    user, local_site=local_site, auto_generated=True)

            body = {
                'revision': diffset.base_commit_id,
                'build_parameters': {
                    'CIRCLE_JOB': 'reviewboard',
                    'REVIEWBOARD_SERVER':
                        get_server_url(local_site=config.local_site),
                    'REVIEWBOARD_REVIEW_REQUEST': review_request.display_id,
                    'REVIEWBOARD_DIFF_REVISION': diffset.revision,
                    'REVIEWBOARD_API_TOKEN': token.token,
                    'REVIEWBOARD_STATUS_UPDATE_ID': status_update.pk,
                },
            }

            if config.local_site:
                body['build_parameters']['REVIEWBOARD_LOCAL_SITE'] = \
                    config.local_site.name

            request = URLRequest(
                url,
                body=json.dumps(body),
                headers={
                    'Accept': 'application/json',
                    'Content-Type': 'application/json',
                },
                method='POST')

            u = urlopen(request)

            data = json.loads(u.read())

            status_update.url = data['build_url']
            status_update.url_text = 'View Build'
            status_update.save()
Beispiel #49
0
def hook_close_submitted(request, local_site_name=None,
                         repository_id=None,
                         hosting_service_id=None):
    """Close review requests as submitted after a push.

    Args:
        request (django.http.HttpRequest):
            The request from the RB Gateway webhook.

        local_site_name (unicode, optional):
            The local site name, if available.

        repository_id (int, optional):
            The ID of the repository, if available.

        hosting_service_id (unicode, optional):
            The ID of the hosting service.

    Returns:
        django.http.HttpResponse;
        A response for the request.
    """
    hook_event = request.META.get('HTTP_X_RBG_EVENT')

    if hook_event == 'ping':
        return HttpResponse()
    elif hook_event != 'push':
        return HttpResponseBadRequest(
            'Only "ping" and "push" events are supported.')

    repository = get_repository_for_hook(repository_id, hosting_service_id,
                                         local_site_name)

    sig = request.META.get('HTTP_X_RBG_SIGNATURE', '')
    m = hmac.new(repository.get_or_create_hooks_uuid().encode('utf-8'),
                 request.body,
                 hashlib.sha1)

    if not hmac.compare_digest(m.hexdigest(), sig):
        return HttpResponseBadRequest('Bad signature.')

    try:
        payload = json.loads(request.body)
    except ValueError as e:
        logging.error('The payload is not in JSON format: %s', e)
        return HttpResponseBadRequest('Invalid payload format.')

    if 'commits' not in payload:
        return HttpResponseBadRequest('Invalid payload; expected "commits".')

    server_url = get_server_url(request=request)
    review_request_ids_to_commits = defaultdict(list)

    for commit in payload['commits']:
        commit_id = commit.get('id')
        commit_message = commit.get('message')
        review_request_id = get_review_request_id(
            commit_message, server_url, commit_id, repository)

        targets = commit['target']

        if 'tags' in targets and targets['tags']:
            target = targets['tags'][0]
        elif 'bookmarks' in targets and targets['bookmarks']:
            target = targets['bookmarks'][0]
        elif 'branch' in targets:
            target = targets['branch']
        else:
            target = ''

        if target:
            target_str = '%s (%s)' % (target, commit_id[:7])
        else:
            target_str = commit_id[:7]

        review_request_ids_to_commits[review_request_id].append(target_str)

    if review_request_ids_to_commits:
        close_all_review_requests(review_request_ids_to_commits,
                                  local_site_name,
                                  repository,
                                  hosting_service_id)

    return HttpResponse()
Beispiel #50
0
def send_review_mail(user, review_request, subject, in_reply_to,
                     to_field, cc_field, text_template_name,
                     html_template_name, context=None, extra_headers=None):
    """Format and send an e-mail out.

    Args:
        user (django.contrib.auth.models.User):
            The user who is sending the e-mail.

        review_request (reviewboard.reviews.models.ReviewRequest):
            The review request that the e-mail is about.

        subject (unicode):
            The subject of the e-mail address.

        in_reply_to (unicode):
            The e-mail message ID for threading.

        to_field (list):
            The recipients to send the e-mail to. This should be a list of
            :py:class:`Users <django.contrib.auth.models.User>` and
            :py:class:`Groups <reviewboard.reviews.models.Group>`.

        cc_field (list):
            The addresses to be CC'ed on the e-mail. This should be a list of
            :py:class:`Users <django.contrib.auth.models.User>` and
            :py:class:`Groups <reviewboard.reviews.models.Group>`.

        text_template_name (unicode):
            The name for the text e-mail template.

        html_template_name (unicode):
            The name for the HTML e-mail template.

        context (dict):
            Optional extra context to provide to the template.

        extra_headers (dict):
            Either a dict or
            :py:class:`~django.utils.datastructures.MultiValueDict` providing
            additional headers to send with the e-mail.

    Returns:
        unicode: The resulting e-mail message ID.
    """
    current_site = Site.objects.get_current()
    local_site = review_request.local_site
    from_email = get_email_address_for_user(user)

    to_field = recipients_to_addresses(to_field, review_request.id)
    cc_field = recipients_to_addresses(cc_field, review_request.id) - to_field

    if not user.should_send_own_updates():
        user_email = get_email_address_for_user(user)
        to_field.discard(user_email)
        cc_field.discard(user_email)

    if not to_field and not cc_field:
        # Nothing to send.
        return

    siteconfig = current_site.config.get()
    domain_method = siteconfig.get("site_domain_method")

    if not context:
        context = {}

    context['user'] = user
    context['domain'] = current_site.domain
    context['domain_method'] = domain_method
    context['review_request'] = review_request

    if review_request.local_site:
        context['local_site_name'] = review_request.local_site.name

    text_body = render_to_string(text_template_name, context)
    html_body = render_to_string(html_template_name, context)

    base_url = get_server_url(local_site=local_site)

    headers = MultiValueDict({
        'X-ReviewBoard-URL': [base_url],
        'X-ReviewRequest-URL': [urljoin(base_url,
                                        review_request.get_absolute_url())],
        'X-ReviewGroup': [', '.join(group.name for group in
                                    review_request.target_groups.all())],
    })

    if extra_headers:
        if not isinstance(extra_headers, MultiValueDict):
            extra_headers = MultiValueDict(
                (key, [value])
                for (key, value) in six.iteritems(extra_headers)
            )

        headers.update(extra_headers)

    if review_request.repository:
        headers['X-ReviewRequest-Repository'] = review_request.repository.name

    latest_diffset = review_request.get_latest_diffset()

    if latest_diffset:
        modified_files = set()

        for filediff in latest_diffset.files.all():
            if filediff.deleted or filediff.copied or filediff.moved:
                modified_files.add(filediff.source_file)

            if filediff.is_new or filediff.copied or filediff.moved:
                modified_files.add(filediff.dest_file)

        for filename in modified_files:
            headers.appendlist('X-ReviewBoard-Diff-For', filename)

    sender = None

    if settings.DEFAULT_FROM_EMAIL:
        sender = build_email_address(user.get_full_name(),
                                     settings.DEFAULT_FROM_EMAIL)

        if sender == from_email:
            # RFC 2822 states that we should only include Sender if the
            # two are not equal.
            sender = None

    message = EmailMessage(subject.strip(),
                           text_body.encode('utf-8'),
                           html_body.encode('utf-8'),
                           from_email, sender,
                           list(to_field), list(cc_field),
                           in_reply_to, headers)
    try:
        message.send()
    except Exception:
        logging.exception("Error sending e-mail notification with subject "
                          "'%s' on behalf of '%s' to '%s'",
                          subject.strip(),
                          from_email,
                          ','.join(list(to_field) + list(cc_field)))

    return message.message_id
Beispiel #51
0
    def _on_review_request_published(self, sender, review_request, **kwargs):
        """Handle when a review request is published.

        Args:
            sender (object):
                The sender of the signal.

            review_request (reviewboard.reviews.models.ReviewRequest):
                The review request which was published.

            **kwargs (dict):
                Additional keyword arguments.
        """
        review_request_id = review_request.get_display_id()
        diffset = review_request.get_latest_diffset()

        if not diffset:
            return

        # If this was an update to a review request, make sure that there was a
        # diff update in it. Otherwise, Review Bot doesn't care, since Review
        # Bot only deals with diffs.
        changedesc = kwargs.get('changedesc')

        if changedesc is not None:
            fields_changed = changedesc.fields_changed

            if ('diff' not in fields_changed or
                'added' not in fields_changed['diff']):
                return

        from reviewbotext.extension import ReviewBotExtension
        extension = ReviewBotExtension.instance

        matching_configs = [
            config
            for config in self.get_configs(review_request.local_site)
            if config.match_conditions(form_cls=self.config_form_cls,
                                       review_request=review_request)
        ]

        if not matching_configs:
            return

        server_url = get_server_url(local_site=review_request.local_site)

        # TODO: This creates a new session entry. We should figure out a better
        # way for Review Bot workers to authenticate to the server.
        session = extension.login_user()
        user = extension.user

        for config in matching_configs:
            tool_id = config.settings.get('tool')

            try:
                tool = Tool.objects.get(pk=tool_id)
            except Tool.DoesNotExist:
                logging.error('Skipping Review Bot integration config %s (%d) '
                              'because Tool with pk=%d does not exist.',
                              config.name, config.pk, tool_id)

            review_settings = {
                'max_comments': config.settings.get(
                    'max_comments',
                    ReviewBotConfigForm.MAX_COMMENTS_DEFAULT),
                'comment_unmodified': config.settings.get(
                    'comment_on_unmodified_code',
                    ReviewBotConfigForm.COMMENT_ON_UNMODIFIED_CODE_DEFAULT),
                'open_issues': config.settings.get(
                    'open_issues',
                    ReviewBotConfigForm.OPEN_ISSUES_DEFAULT),
            }

            try:
                tool_options = json.loads(
                    config.settings.get('tool_options', '{}'))
            except Exception as e:
                logging.exception('Failed to parse tool_options for Review '
                                  'Bot integration config %s (%d): %s',
                                  config.name, config.pk, e)
                tool_options = {}

            status_update = StatusUpdate.objects.create(
                service_id='reviewbot.%s' % tool.name,
                summary=tool.name,
                description='starting...',
                review_request=review_request,
                change_description=changedesc,
                state=StatusUpdate.PENDING,
                timeout=tool.timeout,
                user=user)

            repository = review_request.repository
            queue = '%s.%s' % (tool.entry_point, tool.version)

            if tool.working_directory_required:
                queue = '%s.%s' % (queue, repository.name)

            extension.celery.send_task(
                'reviewbot.tasks.RunTool',
                kwargs={
                    'server_url': server_url,
                    'session': session,
                    'username': user.username,
                    'review_request_id': review_request_id,
                    'diff_revision': diffset.revision,
                    'status_update_id': status_update.pk,
                    'review_settings': review_settings,
                    'tool_options': tool_options,
                    'repository_name': repository.name,
                    'base_commit_id': diffset.base_commit_id,
                },
                queue=queue)
Beispiel #52
0
    def _on_review_request_published(self, sender, review_request, **kwargs):
        """Handle when a review request is published.

        Args:
            sender (object):
                The sender of the signal.

            review_request (reviewboard.reviews.models.ReviewRequest):
                The review request which was published.

            **kwargs (dict):
                Additional keyword arguments.
        """
        review_request_id = review_request.get_display_id()
        diffset = review_request.get_latest_diffset()

        if not diffset:
            return

        # If this was an update to a review request, make sure that there was a
        # diff update in it. Otherwise, Review Bot doesn't care, since Review
        # Bot only deals with diffs.
        changedesc = kwargs.get('changedesc')

        if changedesc is not None:
            fields_changed = changedesc.fields_changed

            if ('diff' not in fields_changed
                    or 'added' not in fields_changed['diff']):
                return

        from reviewbotext.extension import ReviewBotExtension
        extension = ReviewBotExtension.instance

        matching_configs = [
            config for config in self.get_configs(review_request.local_site)
            if config.match_conditions(form_cls=self.config_form_cls,
                                       review_request=review_request)
        ]

        if not matching_configs:
            return

        server_url = get_server_url(local_site=review_request.local_site)

        # TODO: This creates a new session entry. We should figure out a better
        # way for Review Bot workers to authenticate to the server.
        session = extension.login_user()
        user = extension.user

        for config in matching_configs:
            tool_id = config.settings.get('tool')

            try:
                tool = Tool.objects.get(pk=tool_id)
            except Tool.DoesNotExist:
                logging.error(
                    'Skipping Review Bot integration config %s (%d) '
                    'because Tool with pk=%d does not exist.', config.name,
                    config.pk, tool_id)

            review_settings = {
                'max_comments':
                config.settings.get('max_comments',
                                    ReviewBotConfigForm.MAX_COMMENTS_DEFAULT),
                'comment_unmodified':
                config.settings.get(
                    'comment_on_unmodified_code',
                    ReviewBotConfigForm.COMMENT_ON_UNMODIFIED_CODE_DEFAULT),
                'open_issues':
                config.settings.get('open_issues',
                                    ReviewBotConfigForm.OPEN_ISSUES_DEFAULT),
            }

            try:
                tool_options = json.loads(
                    config.settings.get('tool_options', '{}'))
            except Exception as e:
                logging.exception(
                    'Failed to parse tool_options for Review '
                    'Bot integration config %s (%d): %s', config.name,
                    config.pk, e)
                tool_options = {}

            status_update = StatusUpdate.objects.create(
                service_id='reviewbot.%s' % tool.name,
                summary=tool.name,
                description='starting...',
                review_request=review_request,
                change_description=changedesc,
                state=StatusUpdate.PENDING,
                timeout=tool.timeout,
                user=user)

            repository = review_request.repository
            queue = '%s.%s' % (tool.entry_point, tool.version)

            if tool.working_directory_required:
                queue = '%s.%s' % (queue, repository.name)

            extension.celery.send_task('reviewbot.tasks.RunTool',
                                       kwargs={
                                           'server_url':
                                           server_url,
                                           'session':
                                           session,
                                           'username':
                                           user.username,
                                           'review_request_id':
                                           review_request_id,
                                           'diff_revision':
                                           diffset.revision,
                                           'status_update_id':
                                           status_update.pk,
                                           'review_settings':
                                           review_settings,
                                           'tool_options':
                                           tool_options,
                                           'repository_name':
                                           repository.name,
                                           'base_commit_id':
                                           diffset.base_commit_id,
                                       },
                                       queue=queue)
def handle_commits_published(extension=None, **kwargs):
    """Handle sending 'mozreview.commits.published'.

    This message is only sent when the parent review request, in a set of
    pushed review requests, is published with new commit information.

    This is a useful message for consumers who care about new or modified
    commits being published for review.
    """
    review_request = kwargs.get('review_request')

    if review_request is None:
        return

    commit_data = fetch_commit_data(review_request)

    if (not is_pushed(review_request, commit_data)
            or not is_parent(review_request, commit_data)):
        return

    # Check the change description and only continue if it contains a change
    # to the commit information. Currently change descriptions won't include
    # information about our extra data field, so we'll look for a change to
    # the diff which is mandatory if the commits changed. TODO: Properly use
    # the commit information once we start populating the change description
    # with it.
    #
    # A change description will not exist if this is the first publish of the
    # review request. In that case we know there must be commits since this
    # is a pushed request.
    cd = kwargs.get('changedesc')
    if (cd is not None and ('diff' not in cd.fields_changed
                            or 'added' not in cd.fields_changed['diff'])):
        return

    # We publish both the review repository url as well as the landing
    # ("inbound") repository url. This gives consumers which perform hg
    # operations the option to avoid cloning the review repository, which may
    # be large.
    repo = review_request.repository
    repo_url = repo.path
    landing_repo_url = repo.extra_data.get('landing_repository_url')

    child_rrids = []
    commits = []
    ext_commits = json.loads(commit_data.extra_data.get(COMMITS_KEY, '[]'))

    for rev, rrid in ext_commits:
        child_rrids.append(int(rrid))
        commits.append({
            'rev': rev,
            'review_request_id': int(rrid),
            'diffset_revision': None
        })

    # In order to retrieve the diff revision for each commit we need to fetch
    # their correpsonding child review request.
    review_requests = dict(
        (obj.id, obj)
        for obj in ReviewRequest.objects.filter(pk__in=child_rrids))

    for commit_info in commits:
        # TODO: Every call to get_latest_diffset() makes its own query to the
        # database. It is probably possible to retrieve the diffsets we care
        # about using a single query through Django's ORM, but it's not trivial.
        commit_info['diffset_revision'] = review_requests[
            commit_info['review_request_id']].get_latest_diffset().revision

    msg = base.GenericMessage()
    msg.routing_parts.append('mozreview.commits.published')
    msg.data['parent_review_request_id'] = review_request.id
    msg.data['parent_diffset_revision'] = review_request.get_latest_diffset(
    ).revision
    msg.data['commits'] = commits
    msg.data['repository_url'] = repo_url
    msg.data['landing_repository_url'] = landing_repo_url

    # TODO: Make work with RB localsites.
    msg.data['review_board_url'] = get_server_url()

    publish_message(extension, msg)
def handle_commits_published(extension=None, **kwargs):
    """Handle sending 'mozreview.commits.published'.

    This message is only sent when the parent review request, in a set of
    pushed review requests, is published with new commit information.

    This is a useful message for consumers who care about new or modified
    commits being published for review.
    """
    review_request = kwargs.get('review_request')

    if review_request is None:
        return

    commit_data = fetch_commit_data(review_request)

    if (not is_pushed(review_request, commit_data) or
            not is_parent(review_request, commit_data)):
        return

    # Check the change description and only continue if it contains a change
    # to the commit information. Currently change descriptions won't include
    # information about our extra data field, so we'll look for a change to
    # the diff which is mandatory if the commits changed. TODO: Properly use
    # the commit information once we start populating the change description
    # with it.
    #
    # A change description will not exist if this is the first publish of the
    # review request. In that case we know there must be commits since this
    # is a pushed request.
    cd = kwargs.get('changedesc')
    if (cd is not None and ('diff' not in cd.fields_changed or
                            'added' not in cd.fields_changed['diff'])):
        return

    # We publish both the review repository url as well as the landing
    # ("inbound") repository url. This gives consumers which perform hg
    # operations the option to avoid cloning the review repository, which may
    # be large.
    repo = review_request.repository
    repo_url = repo.path
    landing_repo_url = repo.extra_data.get('landing_repository_url')

    child_rrids = []
    commits = []
    ext_commits = json.loads(commit_data.extra_data.get(COMMITS_KEY, '[]'))

    for rev, rrid in ext_commits:
        child_rrids.append(int(rrid))
        commits.append({
            'rev': rev,
            'review_request_id': int(rrid),
            'diffset_revision': None
        })

    # In order to retrieve the diff revision for each commit we need to fetch
    # their correpsonding child review request.
    review_requests = dict(
        (obj.id, obj) for obj in
        ReviewRequest.objects.filter(pk__in=child_rrids))

    for commit_info in commits:
        # TODO: Every call to get_latest_diffset() makes its own query to the
        # database. It is probably possible to retrieve the diffsets we care
        # about using a single query through Django's ORM, but it's not trivial.
        commit_info['diffset_revision'] = review_requests[
            commit_info['review_request_id']
        ].get_latest_diffset().revision

    msg = base.GenericMessage()
    msg.routing_parts.append('mozreview.commits.published')
    msg.data['parent_review_request_id'] = review_request.id
    msg.data['parent_diffset_revision'] = review_request.get_latest_diffset().revision
    msg.data['commits'] = commits
    msg.data['repository_url'] = repo_url
    msg.data['landing_repository_url'] = landing_repo_url

    # TODO: Make work with RB localsites.
    msg.data['review_board_url'] = get_server_url()

    publish_message(extension, msg)
def hook_close_submitted(request,
                         local_site_name=None,
                         repository_id=None,
                         hosting_service_id=None):
    """Close review requests as submitted after a push.

    Args:
        request (django.http.HttpRequest):
            The request from the RB Gateway webhook.

        local_site_name (unicode, optional):
            The local site name, if available.

        repository_id (int, optional):
            The ID of the repository, if available.

        hosting_service_id (unicode, optional):
            The ID of the hosting service.

    Returns:
        django.http.HttpResponse;
        A response for the request.
    """
    hook_event = request.META.get('HTTP_X_RBG_EVENT')

    if hook_event == 'ping':
        return HttpResponse()
    elif hook_event != 'push':
        return HttpResponseBadRequest(
            'Only "ping" and "push" events are supported.')

    repository = get_repository_for_hook(repository_id, hosting_service_id,
                                         local_site_name)

    sig = request.META.get('HTTP_X_RBG_SIGNATURE', '')
    m = hmac.new(repository.get_or_create_hooks_uuid().encode('utf-8'),
                 request.body, hashlib.sha1)

    if not hmac.compare_digest(m.hexdigest(), sig):
        return HttpResponseBadRequest('Bad signature.')

    try:
        payload = json.loads(request.body.decode('utf-8'))
    except ValueError as e:
        logging.error('The payload is not in JSON format: %s', e)
        return HttpResponseBadRequest('Invalid payload format.')

    if 'commits' not in payload:
        return HttpResponseBadRequest('Invalid payload; expected "commits".')

    server_url = get_server_url(request=request)
    review_request_ids_to_commits = defaultdict(list)

    for commit in payload['commits']:
        commit_id = commit.get('id')
        commit_message = commit.get('message')
        review_request_id = get_review_request_id(commit_message, server_url,
                                                  commit_id, repository)

        targets = commit['target']

        if 'tags' in targets and targets['tags']:
            target = targets['tags'][0]
        elif 'bookmarks' in targets and targets['bookmarks']:
            target = targets['bookmarks'][0]
        elif 'branch' in targets:
            target = targets['branch']
        else:
            target = ''

        if target:
            target_str = '%s (%s)' % (target, commit_id[:7])
        else:
            target_str = commit_id[:7]

        review_request_ids_to_commits[review_request_id].append(target_str)

    if review_request_ids_to_commits:
        close_all_review_requests(review_request_ids_to_commits,
                                  local_site_name, repository,
                                  hosting_service_id)

    return HttpResponse()
Beispiel #56
0
def send_review_mail(user,
                     review_request,
                     subject,
                     in_reply_to,
                     to_field,
                     cc_field,
                     text_template_name,
                     html_template_name,
                     context=None,
                     extra_headers=None):
    """Format and send an e-mail out.

    Args:
        user (django.contrib.auth.models.User):
            The user who is sending the e-mail.

        review_request (reviewboard.reviews.models.ReviewRequest):
            The review request that the e-mail is about.

        subject (unicode):
            The subject of the e-mail address.

        in_reply_to (unicode):
            The e-mail message ID for threading.

        to_field (list):
            The recipients to send the e-mail to. This should be a list of
            :py:class:`Users <django.contrib.auth.models.User>` and
            :py:class:`Groups <reviewboard.reviews.models.Group>`.

        cc_field (list):
            The addresses to be CC'ed on the e-mail. This should be a list of
            :py:class:`Users <django.contrib.auth.models.User>` and
            :py:class:`Groups <reviewboard.reviews.models.Group>`.

        text_template_name (unicode):
            The name for the text e-mail template.

        html_template_name (unicode):
            The name for the HTML e-mail template.

        context (dict):
            Optional extra context to provide to the template.

        extra_headers (dict):
            Either a dict or
            :py:class:`~django.utils.datastructures.MultiValueDict` providing
            additional headers to send with the e-mail.

    Returns:
        unicode: The resulting e-mail message ID.
    """
    current_site = Site.objects.get_current()
    local_site = review_request.local_site
    from_email = build_email_address_for_user(user)

    to_field = recipients_to_addresses(to_field, review_request.id)
    cc_field = recipients_to_addresses(cc_field, review_request.id) - to_field

    if not user.should_send_own_updates():
        user_email = build_email_address_for_user(user)
        to_field.discard(user_email)
        cc_field.discard(user_email)

    if not to_field and not cc_field:
        # Nothing to send.
        return

    siteconfig = current_site.config.get()
    domain_method = siteconfig.get("site_domain_method")

    if not context:
        context = {}

    context['user'] = user
    context['domain'] = current_site.domain
    context['domain_method'] = domain_method
    context['review_request'] = review_request

    if review_request.local_site:
        context['local_site_name'] = review_request.local_site.name

    text_body = render_to_string(text_template_name, context)
    html_body = render_to_string(html_template_name, context)

    base_url = get_server_url(local_site=local_site)

    headers = MultiValueDict({
        'X-ReviewBoard-URL': [base_url],
        'X-ReviewRequest-URL':
        [urljoin(base_url, review_request.get_absolute_url())],
        'X-ReviewGroup': [
            ', '.join(group.name
                      for group in review_request.target_groups.all())
        ],
    })

    if extra_headers:
        if not isinstance(extra_headers, MultiValueDict):
            extra_headers = MultiValueDict(
                (key, [value])
                for (key, value) in six.iteritems(extra_headers))

        headers.update(extra_headers)

    if review_request.repository:
        headers['X-ReviewRequest-Repository'] = review_request.repository.name

    latest_diffset = review_request.get_latest_diffset()

    if latest_diffset:
        modified_files = set()

        for filediff in latest_diffset.files.all():
            if filediff.deleted or filediff.copied or filediff.moved:
                modified_files.add(filediff.source_file)

            if filediff.is_new or filediff.copied or filediff.moved:
                modified_files.add(filediff.dest_file)

        for filename in modified_files:
            headers.appendlist('X-ReviewBoard-Diff-For', filename)

    subject = subject.strip()
    to_field = list(to_field)
    cc_field = list(cc_field)

    if settings.DEFAULT_FROM_EMAIL:
        sender = build_email_address(full_name=user.get_full_name(),
                                     email=settings.DEFAULT_FROM_EMAIL)
    else:
        sender = None

    message = EmailMessage(subject=subject,
                           text_body=text_body.encode('utf-8'),
                           html_body=html_body.encode('utf-8'),
                           from_email=from_email,
                           sender=sender,
                           to=to_field,
                           cc=cc_field,
                           in_reply_to=in_reply_to,
                           headers=headers)

    try:
        message.send()
    except Exception:
        logging.exception(
            "Error sending e-mail notification with subject "
            "'%s' on behalf of '%s' to '%s'", subject, from_email,
            ','.join(to_field + cc_field))

    return message.message_id