Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDPR #3431

Closed
wants to merge 29 commits into from
Closed

GDPR #3431

Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
381f7a4
add a new setting: privacy.privacy-statement
Naugrimm Jan 23, 2019
831ed3b
add a new setting: privacy.imprint, move routes to the correct contro…
Naugrimm Jan 23, 2019
ad01aab
l11n
Naugrimm Jan 23, 2019
c3bd8ff
add privacy/imprint link to emails
Naugrimm Jan 23, 2019
2b785fe
styleci
Naugrimm Jan 23, 2019
e3d322b
CachetHQ/Cachet#3054 add footer to subscribe/manage subscription routes
Naugrimm Jan 24, 2019
00abf05
add checkbox "accept privacy statement" when adding subscribers
Naugrimm Jan 24, 2019
21e6b4c
add settings.about_app to the subscribe.subscribe route
Naugrimm Jan 24, 2019
2f49657
CachetHQ/Cachet#3102 fix redirects when adding subscribers/verifying …
Naugrimm Jan 24, 2019
eba7e80
CachetHQ/Cachet/#3102 add List-Unsubscribe header
Naugrimm Jan 24, 2019
cbc1024
add a button on the new privacy settings page to remove unverified su…
Naugrimm Jan 24, 2019
e2d9ae8
add laravel-ide-helper and gitignore generated files
Naugrimm Jan 25, 2019
9f17e7b
fix missing config key
Naugrimm Jan 25, 2019
6b3b6c9
styleci
Naugrimm Jan 25, 2019
36b2b9d
fix unit tests.
Naugrimm Jan 25, 2019
071437e
Revert "add laravel-ide-helper and gitignore generated files"
Naugrimm Jan 28, 2019
aa3b4b4
automatically set the privacy statement to "accepted" if the setting …
Naugrimm Jan 28, 2019
074b3c8
hide the "privacy statement" and "imprint" link if the respective set…
Naugrimm Jan 28, 2019
de7de25
show an error page if no privacy statement/imprint is configured
Naugrimm Jan 28, 2019
e1f0b51
use Illuminate\Support\Str instead of the helper function
Naugrimm Jan 28, 2019
1325d89
set author
Naugrimm Jan 28, 2019
886e2a7
add Markdown-Icon to the textareas
Naugrimm Jan 28, 2019
6d257fc
do not alias axios in the Vue app
Naugrimm Jan 28, 2019
d74e65f
npm run dev
Naugrimm Jan 28, 2019
62d9854
use CarbonInterval->totalDays instead of "totalDayz"
Naugrimm Jan 28, 2019
f0ce6a0
styleci
Naugrimm Jan 28, 2019
9120b97
revert changes to compiled assets
Naugrimm Feb 3, 2019
934d32e
merge 2.4
Naugrimm Jan 26, 2020
21348ac
fix accesslint issues
Naugrimm Jan 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions app/Bus/Commands/Subscriber/SubscribeSubscriberCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,21 @@ final class SubscribeSubscriberCommand
*/
public $subscriptions;

/**
* If the subscriber accepted the privacy statement.
*
* @var bool
*/
public $acceptPrivacyStatement;

/**
* The validation rules.
*
* @var array
*/
public $rules = [
'email' => 'required|email',
'email' => 'required|email',
'acceptPrivacyStatement' => 'required|accepted',
];

/**
Expand All @@ -54,13 +62,15 @@ final class SubscribeSubscriberCommand
* @param string $email
* @param bool $verified
* @param array|null $subscriptions
* @param bool $acceptPrivacyStatement
*
* @return void
*/
public function __construct($email, $verified = false, $subscriptions = null)
public function __construct($email, $verified = false, $subscriptions = null, $acceptPrivacyStatement = false)
{
$this->email = $email;
$this->verified = $verified;
$this->subscriptions = $subscriptions;
$this->acceptPrivacyStatement = $acceptPrivacyStatement;
}
}
34 changes: 34 additions & 0 deletions app/Bus/Handlers/Events/MessageSending.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CachetHQ\Cachet\Bus\Handlers\Events;

/**
* This class is called immediately before sending a message into the mail channel.
*
* @author Erik Anders <[email protected]>
*/
class MessageSending
{
/**
* Handle the any actions that need storing.
*
* @param \Illuminate\Mail\Events\MessageSending $event
*
* @return void
*/
public function handle($event)
{
if ($unsubscribeUrl = $event->data['unsubscribeUrl'] ?? null) {
$event->message->getHeaders()->addTextHeader('List-Unsubscribe', '<'.$unsubscribeUrl.'>');
}
}
}
2 changes: 1 addition & 1 deletion app/Foundation/Providers/ComposerServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function boot(Factory $factory)
{
$factory->composer('*', AppComposer::class);
$factory->composer('*', CurrentUserComposer::class);
$factory->composer(['index', 'single-incident', 'subscribe.*', 'signup', 'dashboard.settings.theme', 'notifications::email', 'single-schedule', 'errors.*'], ThemeComposer::class);
$factory->composer(['index', 'imprint', 'privacy', 'single-incident', 'subscribe.*', 'signup', 'dashboard.settings.theme', 'notifications::email', 'single-schedule', 'errors.*'], ThemeComposer::class);
$factory->composer('dashboard.*', DashboardComposer::class);
$factory->composer(['setup.*', 'dashboard.settings.localization'], TimezoneLocaleComposer::class);

Expand Down
3 changes: 3 additions & 0 deletions app/Foundation/Providers/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,8 @@ class EventServiceProvider extends ServiceProvider
'CachetHQ\Cachet\Bus\Events\User\UserWasWelcomedEvent' => [
//
],
'Illuminate\Mail\Events\MessageSending' => [
'CachetHQ\Cachet\Bus\Handlers\Events\MessageSending',
],
];
}
10 changes: 9 additions & 1 deletion app/Http/Controllers/Api/SubscriberController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use GrahamCampbell\Binput\Facades\Binput;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

Expand Down Expand Up @@ -50,7 +51,14 @@ public function store()
$verified = Binput::get('verify', app(Repository::class)->get('setting.skip_subscriber_verification'));

try {
$subscriber = execute(new SubscribeSubscriberCommand(Binput::get('email'), $verified, Binput::get('components', null)));
$subscriber = execute(new SubscribeSubscriberCommand(
Binput::get('email'),
$verified,
Binput::get('components', null),
// set the privacy statement to "accepted" when it is not given in the input
// and the privacy_statement setting is empty
Binput::get('acceptPrivacyStatement', !Config::get('setting.privacy_statement'))
));
} catch (QueryException $e) {
throw new BadRequestHttpException();
}
Expand Down
49 changes: 49 additions & 0 deletions app/Http/Controllers/Dashboard/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@

namespace CachetHQ\Cachet\Http\Controllers\Dashboard;

use CachetHQ\Cachet\Bus\Commands\Subscriber\UnsubscribeSubscriberCommand;
use CachetHQ\Cachet\Bus\Commands\System\Config\UpdateConfigCommand;
use CachetHQ\Cachet\Integrations\Contracts\Credits;
use CachetHQ\Cachet\Models\Subscriber;
use CachetHQ\Cachet\Models\User;
use CachetHQ\Cachet\Notifications\System\SystemTestNotification;
use CachetHQ\Cachet\Settings\Repository;
use Exception;
use GrahamCampbell\Binput\Facades\Binput;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
Expand Down Expand Up @@ -83,6 +86,12 @@ public function __construct()
'icon' => 'ion-lock-combination',
'active' => false,
],
'privacy' => [
'title' => trans('dashboard.settings.privacy.privacy'),
'url' => cachet_route('dashboard.settings.privacy'),
'icon' => 'ion-ios-glasses',
'active' => false,
],
'analytics' => [
'title' => trans('dashboard.settings.analytics.analytics'),
'url' => cachet_route('dashboard.settings.analytics'),
Expand Down Expand Up @@ -221,6 +230,46 @@ public function showSecurityView()
->withUnsecureUsers($unsecureUsers);
}

/**
* Shows the settings privacy view.
*
* @return \Illuminate\View\View
*/
public function showPrivacyView()
{
$this->subMenu['privacy']['active'] = true;

Session::flash('redirect_to', $this->subMenu['privacy']['url']);

$cleanupInterval = Config::get('setting.unverified_cleanup_interval');

return View::make('dashboard.settings.privacy')
->withPageTitle(trans('dashboard.settings.privacy.privacy').' - '.trans('dashboard.dashboard'))
->withCleanupInterval($cleanupInterval)
->withUnverifiedSubscriberCount(Subscriber::notVerifiedFor($cleanupInterval)->count())
->withSubMenu($this->subMenu);
}

public function removeUnverifiedSubscribers()
{
$cleanupInterval = Config::get('setting.unverified_cleanup_interval');

Subscriber::notVerifiedFor($cleanupInterval)->get()->each(function ($subscriber) {
execute(new UnsubscribeSubscriberCommand($subscriber));
});

return cachet_redirect('dashboard.settings.privacy')
->withSuccess(trans('dashboard.notifications.awesome'));
}

public function getMarkdownPreview(Request $request)
{
return View::make('partials.markdown-preview')
->withMarkdown(
$request->markdown
);
}

/**
* Shows the settings stylesheet view.
*
Expand Down
40 changes: 39 additions & 1 deletion app/Http/Controllers/StatusPageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
use CachetHQ\Cachet\Repositories\Metric\MetricRepository;
use CachetHQ\Cachet\Services\Dates\DateFactory;
use GrahamCampbell\Binput\Facades\Binput;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
use Jenssegers\Date\Date;
use McCool\LaravelAutoPresenter\Facades\AutoPresenter;

Expand Down Expand Up @@ -209,4 +209,42 @@ public function showComponentBadge(Component $component)

return Response::make($badge, 200, ['Content-Type' => 'image/svg+xml']);
}

/**
* Show the privacy statement.
*
* @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
*/
public function showPrivacyStatement()
{
$privacyStatement = trim(Config::get('setting.privacy_statement', ''));
if (!$privacyStatement) {
return abort(404);
}
if (Str::startsWith($privacyStatement, ['http://', 'https://']) && filter_var($privacyStatement, FILTER_VALIDATE_URL)) {
return redirect($privacyStatement);
Naugrimm marked this conversation as resolved.
Show resolved Hide resolved
}

return View::make('privacy')
->withPrivacyStatement($privacyStatement);
}

/**
* Show the imprint.
*
* @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse|\Illuminate\View\View
*/
public function showImprint()
{
$imprint = trim(Config::get('setting.imprint', ''));
if (!$imprint) {
return abort(404);
}
if (Str::startsWith($imprint, ['http://', 'https://']) && filter_var($imprint, FILTER_VALIDATE_URL)) {
return redirect($imprint);
}

return View::make('imprint')
->withImprint($imprint);
}
}
9 changes: 6 additions & 3 deletions app/Http/Controllers/SubscribeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ public function postSubscribe()
$email = Binput::get('email');
$subscriptions = Binput::get('subscriptions');
$verified = app(Repository::class)->get('setting.skip_subscriber_verification');
// set the privacy statement to "accepted" when it is not given in the input
// and the privacy_statement setting is empty
$acceptPrivacyStatement = Binput::get('acceptPrivacyStatement', !Config::get('setting.privacy_statement'));

try {
$subscription = execute(new SubscribeSubscriberCommand($email, $verified));
$subscription = execute(new SubscribeSubscriberCommand($email, $verified, $subscriptions, $acceptPrivacyStatement));
} catch (ValidationException $e) {
return cachet_redirect('status-page')
->withInput(Binput::all())
Expand All @@ -92,7 +95,7 @@ public function postSubscribe()
return cachet_redirect('status-page')->withSuccess(trans('cachet.subscriber.email.already-subscribed', ['email' => $email]));
}

return cachet_redirect('subscribe.manage', $subscription->verify_code)
return cachet_redirect('status-page')
->withSuccess(sprintf('%s %s', trans('dashboard.notifications.awesome'), trans('cachet.subscriber.email.subscribed')));
}

Expand All @@ -119,7 +122,7 @@ public function getVerify($code = null)
execute(new VerifySubscriberCommand($subscriber));
}

return cachet_redirect('status-page')
return cachet_redirect('subscribe.manage', ['code' => $subscriber->verify_code])
->withSuccess(sprintf('%s %s', trans('dashboard.notifications.awesome'), trans('cachet.subscriber.email.verified')));
}

Expand Down
12 changes: 12 additions & 0 deletions app/Http/Routes/Dashboard/SettingRoutes.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ public function map(Registrar $router)
'as' => 'get:dashboard.settings.security',
'uses' => 'SettingsController@showSecurityView',
]);
$router->get('privacy', [
'as' => 'get:dashboard.settings.privacy',
'uses' => 'SettingsController@showPrivacyView',
]);
$router->post('privacy/clean-unverified-subscribers', [
'as' => 'post:dashboard.settings.privacy.remove-unverified-subscribers',
'uses' => 'SettingsController@removeUnverifiedSubscribers',
]);
$router->get('markdown/preview', [
'as' => 'get:dashboard.settings.markdown-preview',
'uses' => 'SettingsController@getMarkdownPreview',
]);
$router->get('theme', [
'as' => 'get:dashboard.settings.theme',
'uses' => 'SettingsController@showThemeView',
Expand Down
10 changes: 10 additions & 0 deletions app/Http/Routes/StatusPageRoutes.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ public function map(Registrar $router)
'as' => 'get:component_shield',
'uses' => 'StatusPageController@showComponentBadge',
]);

$router->get('/imprint', [
'as' => 'get:imprint',
'uses' => 'StatusPageController@showImprint',
]);

$router->get('/privacy', [
'as' => 'get:privacy',
'uses' => 'StatusPageController@showPrivacyStatement',
]);
});
}
}
18 changes: 18 additions & 0 deletions app/Models/Subscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@

use AltThree\Validator\ValidatingTrait;
use CachetHQ\Cachet\Presenters\SubscriberPresenter;
use Carbon\CarbonInterval;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use McCool\LaravelAutoPresenter\HasPresenter;

/**
Expand Down Expand Up @@ -122,6 +124,22 @@ public function scopeIsVerified(Builder $query)
return $query->whereNotNull('verified_at');
}

/**
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $interval an ISO8601 duration @see \DateInterval
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeNotVerifiedFor(Builder $query, string $interval = 'P1M')
{
$maxAge = Carbon::now()->subDays(
CarbonInterval::make($interval)->totalDays
);

return $query->whereNull('verified_at')
->where('created_at', '<', $maxAge);
}

/**
* Scope global subscribers.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ public function toMail($notifiable)
'unsubscribeUrl' => cachet_route('subscribe.unsubscribe', $notifiable->verify_code),
'manageSubscriptionText' => trans('cachet.subscriber.manage_subscription'),
'manageSubscriptionUrl' => cachet_route('subscribe.manage', $notifiable->verify_code),
'privacyText' => trans('forms.settings.privacy.privacy-statement'),
'privacyUrl' => cachet_route('privacy'),
'imprintText' => trans('forms.settings.privacy.imprint'),
'imprintUrl' => cachet_route('imprint'),
]);
}

Expand Down
4 changes: 4 additions & 0 deletions app/Notifications/Incident/NewIncidentNotification.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ public function toMail($notifiable)
'unsubscribeUrl' => cachet_route('subscribe.unsubscribe', $notifiable->verify_code),
'manageSubscriptionText' => trans('cachet.subscriber.manage_subscription'),
'manageSubscriptionUrl' => cachet_route('subscribe.manage', $notifiable->verify_code),
'privacyText' => trans('forms.settings.privacy.privacy-statement'),
'privacyUrl' => cachet_route('privacy'),
'imprintText' => trans('forms.settings.privacy.imprint'),
'imprintUrl' => cachet_route('imprint'),
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ public function toMail($notifiable)
'unsubscribeUrl' => cachet_route('subscribe.unsubscribe', $notifiable->verify_code),
'manageSubscriptionText' => trans('cachet.subscriber.manage_subscription'),
'manageSubscriptionUrl' => cachet_route('subscribe.manage', $notifiable->verify_code),
'privacyText' => trans('forms.settings.privacy.privacy-statement'),
'privacyUrl' => cachet_route('privacy'),
'imprintText' => trans('forms.settings.privacy.imprint'),
'imprintUrl' => cachet_route('imprint'),
]);
}

Expand Down
4 changes: 4 additions & 0 deletions app/Notifications/Schedule/NewScheduleNotification.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public function toMail($notifiable)
'unsubscribeUrl' => cachet_route('subscribe.unsubscribe', $notifiable->verify_code),
'manageSubscriptionText' => trans('cachet.subscriber.manage_subscription'),
'manageSubscriptionUrl' => cachet_route('subscribe.manage', $notifiable->verify_code),
'privacyText' => trans('forms.settings.privacy.privacy-statement'),
'privacyUrl' => cachet_route('privacy'),
'imprintText' => trans('forms.settings.privacy.imprint'),
'imprintUrl' => cachet_route('imprint'),
]);
}

Expand Down
Loading