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)
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)
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 }
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 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, }))
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,))
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()
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()
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, }))
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()
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(), }, ), )
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 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])
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])
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), }))
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), })
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, }, } }
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])
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()
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')
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()
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()
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()
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())
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', })
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(), }))
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())
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)
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 ])
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(), }))
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)
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)
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
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
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
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()
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()
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()
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()
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
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 _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()
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