8000 feature #38552 [Security][Notifier] Added integration of Login Link w… · symfony/symfony@a428b01 · GitHub
[go: up one dir, main page]

Skip to content

Commit a428b01

Browse files
committed
feature #38552 [Security][Notifier] Added integration of Login Link with the Notifier component (wouterj)
This PR was squashed before being merged into the 5.x branch. Discussion ---------- [Security][Notifier] Added integration of Login Link with the Notifier component | Q | A | ------------- | --- | Branch? | 5.x (5.2 hopefully?) | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - This adds a `LoginLinkNotification` that uses the `NotificationEmail` and integrates with the notifier component. This makes it much easier to use the login link functionality, as it provides a default email and sms implementation. ```php class AuthController extends AbstractController { /** @route("/login", name="login") */ public function login(LoginLinkHandlerInterface $loginLinkHandler, UserRepository $userRepository, Request $request, NotifierInterface $notifier) { if (!$request->isMethod('POST')) { return $this->redirect('/'); } $user = $userRepository->findOneBy(['email' => $request->get('email')]); if (!$user) { return new Response('User not found'); } $loginLink = $loginLinkHandler->createLoginLink($user); $notifier->send(new LoginLinkNotification($loginLink, 'Welcome to ACME!'), new Recipient($user->getEmail())); return new Response('Login link send!'); } /** @route("/login/check", name="check_login") */ public function loginCheck() { throw new \BadMethodCallException(); } } ``` ![image](https://user-images.githubusercontent.com/749025/95884718-be9d0780-0d7c-11eb-88ff-36b6b3108ca6.png) --- The `NotificationEmail` is slightly changed, to allow bypassing the logging-related functionality. Also, @weaverryan suggested to remove the "created by Symfony" footer as this email is meant to be sent to all users of a service. Commits ------- 04ef565 [Security][Notifier] Added integration of Login Link with the Notifier component
2 parents ffbb988 + 04ef565 commit a428b01

File tree

5 files changed

+124
-3
lines changed

5 files changed

+124
-3
lines changed

src/Symfony/Bridge/Twig/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ CHANGELOG
1010
* added the `t()` function to easily create `TranslatableMessage` objects
1111
* Added support for extracting messages from the `t()` function
1212
* Added `field_*` Twig functions to access string values from Form fields
13+
* changed the `importance` context option of `NotificationEmail` to allow `null`
1314

1415
5.0.0
1516
-----

src/Symfony/Bridge/Twig/Mime/NotificationEmail.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class NotificationEmail extends TemplatedEmail
3737
'action_url' => null,
3838
'markdown' => false,
3939
'raw' => false,
40+
'footer_text' => 'Notification e-mail sent by Symfony',
4041
];
4142

4243
public function __construct(Headers $headers = null, AbstractPart $body = null)
@@ -57,6 +58,18 @@ public function __construct(Headers $headers = null, AbstractPart $body = null)
5758
parent::__construct($headers, $body);
5859
}
5960

61+
/**
62+
* Creates a NotificationEmail instance that is appropriate to send to normal (non-admin) users.
63+
*/
64+
public static function asPublicEmail(Headers $headers = null, AbstractPart $body = null): self
65+
{
66+
$email = new static($headers, $body);
67+
$email->context['importance'] = null;
68+
$email->context['footer_text'] = null;
69+
70+
return $email;
71+
}
72+
6073
/**
6174
* @return $this
6275
*/
@@ -166,7 +179,9 @@ public function getPreparedHeaders(): Headers
166179

167180
$importance = $this->context['importance'] ?? self::IMPORTANCE_LOW;
168181
$this->priority($this->determinePriority($importance));
169-
$headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject()));
182+
if ($this->context['importance']) {
183+
$headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject()));
184+
}
170185

171186
return $headers;
172187
}

src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.html.twig

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<row>
1717
<columns large="12" small="12">
1818
{% block lead %}
19-
<small><strong>{{ importance|upper }}</strong></small>
19+
{% if importance is not null %}<small><strong>{{ importance|upper }}</strong></small>{% endif %}
2020
<p class="lead">
2121
{{ email.subject }}
2222
</p>
@@ -49,13 +49,15 @@
4949
<wrapper class="secondary">
5050
<spacer size="16"></spacer>
5151
{% block footer %}
52+
{% if footer_text is defined and footer_text is not null %}
5253
<row>
5354
<columns small="12" large="6">
5455
{% block footer_content %}
55-
<p><small>Notification e-mail sent by Symfony</small></p>
56+
<p><small>{{ footer_text }}</small></p>
5657
{% endblock %}
5758
</columns>
5859
</row>
60+
{% endif %}
5961
{% endblock %}
6062
</wrapper>
6163
</container>

src/Symfony/Bridge/Twig/Tests/Mime/NotificationEmailTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public function test()
2626
'markdown' => true,
2727
'raw' => false,
2828
'a' => 'b',
29+
'footer_text' => 'Notification e-mail sent by Symfony',
2930
], $email->getContext());
3031
}
3132

@@ -47,6 +48,7 @@ public function testSerialize()
4748
'markdown' => false,
4849
'raw' => true,
4950
'a' => 'b',
51+
'footer_text' => 'Notification e-mail sent by Symfony',
5052
], $email->getContext());
5153
}
5254

@@ -63,4 +65,32 @@ public function testSubject()
6365
$headers = $email->getPreparedHeaders();
6466
$this->assertSame('[LOW] Foo', $headers->get('Subject')->getValue());
6567
}
68+
69+
public function testPublicMail()
70+
{
71+
$email = NotificationEmail::asPublicEmail()
72+
->markdown('Foo')
73+
->action('Bar', 'http://example.com/')
74+
->context(['a' => 'b'])
75+
;
76+
77+
$this->assertEquals([
78+
'importance' => null,
79+
'content' => 'Foo',
80+
'exception' => false,
81+
'action_text' => 'Bar',
82+
'action_url' => 'http://example.com/',
83+
'markdown' => true,
84+
'raw' => false,
85+
'a' => 'b',
86+
'footer_text' => null,
87+
], $email->getContext());
88+
}
89+
90+
public function testPublicMailSubject()
91+
{
92+
$email = NotificationEmail::asPublicEmail()->from('me@example.com')->subject('Foo');
93+
$headers = $email->getPreparedHeaders();
94+
$this->assertSame('Foo', $headers->get('Subject')->getValue());
95+
}
6696
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\LoginLink;
13+
14+
use Symfony\Bridge\Twig\Mime\NotificationEmail;
15+
use Symfony\Component\Notifier\Message\EmailMessage;
16+
use Symfony\Component\Notifier\Message\SmsMessage;
17+
use Symfony\Component\Notifier\Notification\EmailNotificationInterface;
18+
use Symfony\Component\Notifier\Notification\Notification;
19+
use Symfony\Component\Notifier\Notification\SmsNotificationInterface;
20+
use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
21+
use Symfony\Component\Notifier\Recipient\SmsRecipientInterface;
22+
23+
/**
24+
* Use this notification to ease sending login link
25+
* emails/SMS using the Notifier component.
26+
*
27+
* @author Wouter de Jong <wouter@wouterj.nl>
28+
*
29+
* @experimental in 5.2
30+
*/
31+
class LoginLinkNotification extends Notification implements EmailNotificationInterface, SmsNotificationInterface
32+
{
33+
private $loginLinkDetails;
34+
35+
public function __construct(LoginLinkDetails $loginLinkDetails, string $subject, array $channels = [])
36+
{
37+
parent::__construct($subject, $channels);
38+
39+
$this->loginLinkDetails = $loginLinkDetails;
40+
}
41+
42+
public function asEmailMessage(EmailRecipientInterface $recipient, string $transport = null): ?EmailMessage
43+
{
44+
if (!class_exists(NotificationEmail::class)) {
45+
throw new \LogicException(sprintf('The "%s" method requires "symfony/twig-bridge:>4.4".', __METHOD__));
46+
}
47+
48+
$email = NotificationEmail::asPublicEmail()
49+
->to($recipient->getEmail())
50+
->subject($this->getSubject())
51+
->content($this->getContent() ?: $this->getDefaultContent('button below'))
52+
->action('Sign in', $this->loginLinkDetails->getUrl())
53+
;
54+
55+
return new EmailMessage($email);
56+
}
57+
58+
public function asSmsMessage(SmsRecipientInterface $recipient, string $transport = null): ?SmsMessage
59+
{
60+
return new SmsMessage($recipient->getPhone(), $this->getDefaultContent('link').' '.$this->loginLinkDetails->getUrl());
61+
}
62+
63+
private function getDefaultContent(string $target): string
64+
{
65+
$duration = $this->loginLinkDetails->getExpiresAt()->getTimestamp() - time();
66+
$durationString = floor($duration / 60).' minute'.($duration > 60 ? 's' : '');
67+
if (($hours = $duration / 3600) >= 1) {
68+
$durationString = floor($hours).' hour'.($hours >= 2 ? 's' : '');
69+
}
70+
71+
return sprintf('Click on the %s to confirm you want to sign in. This link will expire in %s.', $target, $durationString);
72+
}
73+
}

0 commit comments

Comments
 (0)
0