Skip to content

Commit

Permalink
Add an HTTP endpoint for setting the Sentry release ID
Browse files Browse the repository at this point in the history
  • Loading branch information
DieterHolvoet committed Mar 5, 2021
1 parent 140a37e commit 693634f
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Added
- Add an HTTP endpoint for setting the Sentry release ID

## [1.6.2] - 2021-02-18
### Fixed
- Fix backwards incompatible changes after v3 update
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,18 @@ fingerprints etc.
This function is called before the client is created with an options
object. The options object is a configuration container for the Sentry
client.


### Setting the release ID through an HTTP endpoint
This module provides an HTTP endpoint that can be used to set the Sentry release ID without being logged in. This can
be useful when creating a new Sentry release through the API, eg. in a CI pipeline.

The endpoint is `/sentry/set-release` and is secured in the same way as the `core/rebuild.php` script (see
[documentation](https://www.drupal.org/node/2153725)). The release ID can be passed using the `release` query parameter.
Here's an example call:
```
/sentry/set-release?release=exampleproject@62d50f53&timestamp=1614934032&token=XXE6H4wwVC6x5I6QnTPFTj-hSksNfgUpndv9X-3lC7Y
```

## Changelog
All notable changes to this project will be documented in the
[CHANGELOG](CHANGELOG.md) file.
Expand All @@ -79,5 +90,5 @@ If you discover any security-related issues, please email
tracker.

## License
Distributed under the MIT License. See the [LICENSE](LICENSE.md) file
Distributed under the MIT License. See the [LICENSE](LICENSE) file
for more information.
96 changes: 96 additions & 0 deletions src/Controller/SetReleaseController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace Drupal\wmsentry\Controller;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class SetReleaseController implements ContainerInjectionInterface
{
/** @var RedirectDestinationInterface */
protected $destination;
/** @var MessengerInterface */
protected $messenger;
/** @var StateInterface */
protected $state;
/** @var TimeInterface */
protected $time;

public static function create(ContainerInterface $container)
{
$instance = new static();
$instance->destination = $container->get('redirect.destination');
$instance->messenger = $container->get('messenger');
$instance->state = $container->get('state');
$instance->time = $container->get('datetime.time');

return $instance;
}

public function set(Request $request): Response
{
if (!$release = $request->query->get('release')) {
return new Response(
'The release ID needs to be passed through the release query parameter.',
Response::HTTP_BAD_REQUEST
);
}

$isValid = $this->isValidToken(
$request->query->get('token'),
$request->query->get('timestamp')
);

if (!$isValid) {
return new JsonResponse(
'The token and/or timestamp query parameters are incorrect.',
Response::HTTP_BAD_REQUEST
);
}

$this->state->set('wmsentry.release', $release);

return new Response(
'Successfully changed the current Sentry release'
);
}

public function unset(): Response
{
$this->state->delete('wmsentry.release');
$this->messenger->addStatus('Successfully deleted the Sentry release override');

try {
$destination = $this->destination->get();
$url = Url::fromUserInput($destination)->setAbsolute()->toString();
return RedirectResponse::create($url);
} catch (\InvalidArgumentException $e) {
}

return Response::create();
}

protected function isValidToken(?string $token, ?int $timestamp): bool
{
if (!$token || !$timestamp) {
return false;
}

if (($this->time->getRequestTime() - $timestamp) >= 300) {
return false;
}

return hash_equals(Crypt::hmacBase64($timestamp, Settings::get('hash_salt')), $token);
}
}
24 changes: 24 additions & 0 deletions src/Form/SettingsForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\State\State;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;

class SettingsForm extends ConfigFormBase
{
/** @var State */
protected $state;

public static function create(ContainerInterface $container)
{
$instance = parent::create($container);
$instance->state = $container->get('state');

return $instance;
}

public function getFormId()
{
return 'wmsentry_settings';
Expand All @@ -31,6 +45,16 @@ public function buildForm(array $form, FormStateInterface $form_state)
'#default_value' => $config->get('release'),
];

if ($release = $this->state->get('wmsentry.release')) {
$destination = Url::fromRoute('<current>')->toString();
$setUrl = Url::fromRoute('wmsentry.set_release')->toString();
$unsetUrl = Url::fromRoute('wmsentry.unset_release', ['destination' => $destination])->toString();

$form['release']['#disabled'] = true;
$form['release']['#description'] .= sprintf(' <br><b>This value is overridden by the release set
using the <code>%s</code> endpoint. <a href="%s">Remove the override</a>.</b>', $setUrl, $unsetUrl);
}

$form['environment'] = [
'#type' => 'textfield',
'#title' => 'Environment',
Expand Down
16 changes: 15 additions & 1 deletion src/Logger/Sentry.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Drupal\Core\Logger\LogMessageParserInterface;
use Drupal\Core\Logger\RfcLoggerTrait;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\State\StateInterface;
use Drupal\user\UserInterface;
use Drupal\wmsentry\Event\SentryBeforeBreadcrumbEvent;
use Drupal\wmsentry\Event\SentryBeforeSendEvent;
Expand Down Expand Up @@ -50,6 +51,8 @@ class Sentry implements LoggerInterface
protected $parser;
/** @var ClientInterface */
protected $client;
/** @var StateInterface */
protected $state;
/** @var StacktraceBuilder */
protected $stackTraceBuilder;
/** @var EventDispatcherInterface */
Expand All @@ -62,12 +65,14 @@ class Sentry implements LoggerInterface
public function __construct(
ConfigFactoryInterface $config,
LogMessageParserInterface $parser,
StateInterface $state,
EventDispatcherInterface $eventDispatcher,
ModuleHandlerInterface $moduleHandler,
EntityTypeManagerInterface $entityTypeManager
) {
$this->config = $config->get('wmsentry.settings');
$this->parser = $parser;
$this->state = $state;
$this->eventDispatcher = $eventDispatcher;
$this->moduleHandler = $moduleHandler;
$this->entityTypeManager = $entityTypeManager;
Expand Down Expand Up @@ -215,7 +220,7 @@ protected function getClient(): ?ClientInterface
'in_app_include' => $this->normalizePaths($this->config->get('in_app_include') ?? []),
]);

if ($value = $this->config->get('release')) {
if ($value = $this->getRelease()) {
$options->setRelease($value);
}

Expand Down Expand Up @@ -251,6 +256,15 @@ protected function getLogLevel(int $rfc): ?Severity
return null;
}

protected function getRelease(): ?string
{
if ($override = $this->state->get('wmsentry.release')) {
return $override;
}

return $this->config->get('release');
}

protected function formatMessage(string $message, array $context): string
{
/** @see errors.inc */
Expand Down
14 changes: 14 additions & 0 deletions wmsentry.routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,17 @@ wmsentry.settings:
_title: Sentry
requirements:
_permission: 'administer wmsentry settings'

wmsentry.set_release:
path: /sentry/set-release
defaults:
_controller: Drupal\wmsentry\Controller\SetReleaseController::set
requirements:
_access: 'TRUE'

wmsentry.unset_release:
path: /sentry/unset-release
defaults:
_controller: Drupal\wmsentry\Controller\SetReleaseController::unset
requirements:
_permission: 'administer wmsentry settings'
1 change: 1 addition & 0 deletions wmsentry.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ services:
arguments:
- '@config.factory'
- '@logger.log_message_parser'
- '@state'
- '@event_dispatcher'
- '@module_handler'
- '@entity_type.manager'
Expand Down

0 comments on commit 693634f

Please sign in to comment.