Skip to content

Commit 5395cb1

Browse files
authored
SmtpAuthenticationMode.OAuth2 with SmtpUserName (OAuth ClientId) and SmtpPassword (OAuth ClientSecret) (#227)
1 parent 7aaac1a commit 5395cb1

File tree

6 files changed

+215
-11
lines changed

6 files changed

+215
-11
lines changed

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@
1010
Alternative Mail target for [NLog](https://github.com/nlog/nlog) using [MailKit](https://github.com/jstedfast/MailKit). Compatible with .NET standard 2+
1111

1212
Including this package will replace the original mail target and has the
13-
same options as the original mail target, see [docs of the original mailTarget](https://github.com/NLog/NLog/wiki/Mail-Target)
14-
15-
Currently not implemented:
16-
17-
- NTLM auth
13+
same options as the original mail target, see [docs of the original mailTarget](https://github.com/NLog/NLog/wiki/Mail-Target).
14+
But Mailkit does not yet support `SmtpAuthentication = NTLM`.
1815

1916
This library is integration tested with the [SmtpServer NuGet package](https://www.nuget.org/packages/SmtpServer/)
2017

21-
2218
### How to use
2319

2420
1) Install the package:
@@ -47,6 +43,18 @@ See the [NLog Wiki](https://github.com/NLog/NLog/wiki/Mail-Target) for available
4743

4844
Note that the option `skipCertificateValidation="true"` can prevent `AuthenticationException` if your remote certificate for smtpServer is invalid - not recommend!
4945

46+
### OAuth2 Authentication
47+
48+
Mailkit supports `OAuth2` authentication by specifying `SmtpAuthentication = OAuth2` together with:
49+
- `SmtpUserName = ${gdc:OAuthClientId}`
50+
- `SmtpPassword = ${gdc:OAuthClientSecret}`
51+
52+
Before using OAuth2 authentication, make sure to acquire an access token from your email provider (e.g., Gmail, Outlook) and store it in the [Global Diagnostics Context (GDC)](https://github.com/NLog/NLog/wiki/Gdc-layout-renderer) with the key `OAuthClientSecret` (And ensure it is refreshed before expiry).
53+
Alternative store the access token in an environment variable and use the NLog [${environment:variable=OAuthClientSecret}](https://github.com/NLog/NLog/wiki/Environment-layout-renderer) instead of NLog GDC.
54+
55+
- [Using OAuth2 With Microsoft Outlook Exchange](https://github.com/jstedfast/MailKit/blob/master/ExchangeOAuth2.md)
56+
- [Using OAuth2 With Google GMail](https://github.com/jstedfast/MailKit/blob/master/GMailOAuth2.md)
57+
5058
### License
5159
BSD. License of MailKit is MIT
5260

azure-pipelines.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ variables:
1313
Solution: 'src/NLog.MailKit.sln'
1414
BuildPlatform: 'Any CPU'
1515
BuildConfiguration: 'Release'
16-
Version: '6.0.3'
16+
Version: '6.0.4'
1717
FullVersion: '$(Version).$(Build.BuildId)'
1818

1919
steps:

src/NLog.MailKit/MailTarget.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,15 @@ public Layout Body
213213
/// <note type="note">This feature is only available if connected SMTP server supports capability
214214
/// <see cref="SmtpCapabilities.RequireTLS"/> flag when sending the message.</note>
215215
/// </remarks>
216+
/// <docgen category='SMTP Options' order='14' />
216217
public Layout<bool> RequireTLS { get; set; } = false;
217218

218219
/// <summary>
219220
/// Provides a way of specifying the SSL and/or TLS encryption
220221
///
221222
/// If <see cref="EnableSsl" /> is <c>true</c>, then <see cref="SecureSocketOptions.SslOnConnect" /> will be used.
222223
/// </summary>
224+
/// <docgen category='SMTP Options' order='14' />
223225
[DefaultValue(DefaultSecureSocketOption)]
224226
[CLSCompliant(false)]
225227
public Layout<SecureSocketOptions> SecureSocketOption { get; set; } = DefaultSecureSocketOption;
@@ -246,18 +248,20 @@ public Layout Body
246248
/// Gets or sets a value indicating whether NewLine characters in the body should be replaced with <br/> tags.
247249
/// </summary>
248250
/// <remarks>Only happens when <see cref="Html"/> is set to true.</remarks>
251+
/// <docgen category='Message Options' order='100' />
249252
public Layout<bool> ReplaceNewlineWithBrTagInHtml { get; set; } = false;
250253

251254
/// <summary>
252255
/// Gets or sets a value indicating the SMTP client timeout (in milliseconds)
253256
/// </summary>
254257
/// <remarks>Warning: zero is not infinite waiting</remarks>
258+
/// <docgen category='SMTP Options' order='17' />
255259
public Layout<int> Timeout { get; set; } = 10000;
256260

257261
/// <summary>
258262
/// Gets or sets the folder where applications save mail messages to be processed by the local SMTP server.
259263
/// </summary>
260-
/// <docgen category='SMTP Options' order='17' />
264+
/// <docgen category='SMTP Options' order='18' />
261265
public Layout? PickupDirectoryLocation { get; set; }
262266

263267
/// <summary>
@@ -358,7 +362,7 @@ private void SendMailMessage(MimeMessage message, LogEventInfo lastEvent)
358362
client.Timeout = RenderLogEvent(Timeout, lastEvent);
359363

360364
var renderedHost = RenderLogEvent(SmtpServer, lastEvent);
361-
if (string.IsNullOrEmpty(renderedHost))
365+
if (string.IsNullOrWhiteSpace(renderedHost))
362366
{
363367
throw new NLogRuntimeException(string.Format(RequiredPropertyIsEmptyFormat, nameof(SmtpServer)));
364368
}
@@ -401,6 +405,22 @@ private void SendMailMessage(MimeMessage message, LogEventInfo lastEvent)
401405
InternalLogger.Trace("{0}: Authenticate with username '{1}'", this, userName);
402406
client.Authenticate(userName, password);
403407
}
408+
else if (smtpAuthentication == SmtpAuthenticationMode.OAuth2)
409+
{
410+
var userName = RenderLogEvent(SmtpUserName, lastEvent);
411+
var oauth2Token = RenderLogEvent(SmtpPassword, lastEvent);
412+
if (string.IsNullOrWhiteSpace(oauth2Token))
413+
{
414+
throw new NLogRuntimeException(string.Format(RequiredPropertyIsEmptyFormat, nameof(SmtpPassword)));
415+
}
416+
if (string.IsNullOrWhiteSpace(userName))
417+
{
418+
throw new NLogRuntimeException(string.Format(RequiredPropertyIsEmptyFormat, nameof(SmtpUserName)));
419+
}
420+
InternalLogger.Trace("{0}: Authenticate with OAuth2 username '{1}'", this, userName);
421+
var oauth2 = new SaslMechanismOAuth2(userName, oauth2Token);
422+
client.Authenticate(oauth2);
423+
}
404424

405425
client.Send(message);
406426
InternalLogger.Trace("{0}: Sending mail done. Disconnecting", this);
@@ -500,6 +520,11 @@ private void CheckRequiredParameters()
500520
{
501521
throw new NLogConfigurationException("MailTarget - SmtpServer is required");
502522
}
523+
524+
if (smtpAuthentication == SmtpAuthenticationMode.OAuth2 && (SmtpUserName is null || ReferenceEquals(SmtpUserName, Layout.Empty) || SmtpPassword is null || ReferenceEquals(SmtpPassword, Layout.Empty)))
525+
{
526+
throw new NLogConfigurationException("MailTarget - SmtpUserName (OAuth UserName) and SmtpPassword (OAuth AccessToken) is required when SmtpAuthentication = OAuth2");
527+
}
503528
}
504529

505530
/// <summary>

src/NLog.MailKit/NLog.MailKit.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ If the mail target was already available on your platform, this package will ove
2929
<DelaySign>false</DelaySign>
3030
<PackageReleaseNotes>
3131
Changelog:
32-
33-
- Updated NLog v6.0.2
32+
33+
- SmtpAuthenticationMode.OAuth2 with SmtpUserName (OAuth UserName) and SmtpPassword (OAuth AccessToken)
34+
- Updated NLog v6.0.4
3435

3536
See https://github.com/NLog/NLog.MailKit/releases
3637

src/NLog.MailKit/SmtpAuthenticationMode.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,10 @@ public enum SmtpAuthenticationMode
2121
/// NTLM Authentication.
2222
/// </summary>
2323
Ntlm,
24+
25+
/// <summary>
26+
/// OAuth 2.0 (XOAUTH2) Authentication
27+
/// </summary>
28+
OAuth2
2429
}
2530
}

test/NLog.MailKit.Tests/UnitTests/MailTargetTests.cs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ namespace NLog.MailKit.Tests.UnitTests
66
{
77
public class MailTargetTests
88
{
9+
public MailTargetTests()
10+
{
11+
NLog.LogManager.ThrowConfigExceptions = true;
12+
}
13+
914
[Theory]
1015
[InlineData("high", MessagePriority.Urgent)]
1116
[InlineData("HIGH", MessagePriority.Urgent)]
@@ -25,5 +30,165 @@ public void ParseMessagePriorityTests(string input, MessagePriority expected)
2530
// Assert
2631
Assert.Equal(expected, result);
2732
}
33+
34+
35+
[Fact]
36+
public void MailTarget_WithEmptyTo_ThrowsConfigException()
37+
{
38+
var mmt = new MailTarget
39+
{
40+
41+
To = "",
42+
Subject = "Hello from NLog",
43+
SmtpServer = "server1",
44+
SmtpPort = 27,
45+
};
46+
47+
Assert.Throws<NLogConfigurationException>(() =>
48+
new LogFactory().Setup().LoadConfiguration(cfg =>
49+
{
50+
cfg.Configuration.AddRuleForAllLevels(mmt);
51+
})
52+
);
53+
}
54+
55+
[Fact]
56+
public void MailTarget_WithEmptyFrom_ThrowsConfigException()
57+
{
58+
var mmt = new MailTarget
59+
{
60+
From = "",
61+
62+
Subject = "Hello from NLog",
63+
SmtpServer = "server1",
64+
SmtpPort = 27,
65+
};
66+
67+
Assert.Throws<NLogConfigurationException>(() =>
68+
new LogFactory().Setup().LoadConfiguration(cfg =>
69+
{
70+
cfg.Configuration.AddRuleForAllLevels(mmt);
71+
})
72+
);
73+
}
74+
75+
[Fact]
76+
public void MailTarget_WithEmptySmtpServer_ThrowsConfigException()
77+
{
78+
var mmt = new MailTarget
79+
{
80+
81+
82+
Subject = "Hello from NLog",
83+
SmtpServer = "",
84+
SmtpPort = 27,
85+
};
86+
87+
Assert.Throws<NLogConfigurationException>(() =>
88+
new LogFactory().Setup().LoadConfiguration(cfg =>
89+
{
90+
cfg.Configuration.AddRuleForAllLevels(mmt);
91+
})
92+
);
93+
}
94+
95+
[Fact]
96+
public void MailTargetInitialize_WithoutSpecifiedTo_ThrowsConfigException()
97+
{
98+
var mmt = new MailTarget
99+
{
100+
101+
Subject = "Hello from NLog",
102+
SmtpServer = "server1",
103+
SmtpPort = 27,
104+
};
105+
106+
Assert.Throws<NLogConfigurationException>(() =>
107+
new LogFactory().Setup().LoadConfiguration(cfg =>
108+
{
109+
cfg.Configuration.AddRuleForAllLevels(mmt);
110+
})
111+
);
112+
}
113+
114+
[Fact]
115+
public void MailTargetInitialize_WithoutSpecifiedFrom_ThrowsConfigException()
116+
{
117+
var mmt = new MailTarget
118+
{
119+
120+
Subject = "Hello from NLog",
121+
SmtpServer = "server1",
122+
SmtpPort = 27,
123+
};
124+
125+
Assert.Throws<NLogConfigurationException>(() =>
126+
new LogFactory().Setup().LoadConfiguration(cfg =>
127+
{
128+
cfg.Configuration.AddRuleForAllLevels(mmt);
129+
})
130+
);
131+
}
132+
133+
[Fact]
134+
public void MailTargetInitialize_WithoutSpecifiedSmtpServer_ThrowsConfigException()
135+
{
136+
var mmt = new MailTarget
137+
{
138+
139+
140+
Subject = "Hello from NLog",
141+
SmtpPort = 27,
142+
};
143+
144+
Assert.Throws<NLogConfigurationException>(() =>
145+
new LogFactory().Setup().LoadConfiguration(cfg =>
146+
{
147+
cfg.Configuration.AddRuleForAllLevels(mmt);
148+
})
149+
);
150+
}
151+
152+
[Fact]
153+
public void MailTargetInitialize_WithSmtpAuthenticationModeNtlm_ThrowsConfigException()
154+
{
155+
var mmt = new MailTarget
156+
{
157+
158+
159+
Subject = "Hello from NLog",
160+
SmtpServer = "server1",
161+
SmtpPort = 27,
162+
SmtpAuthentication = SmtpAuthenticationMode.Ntlm,
163+
};
164+
165+
Assert.Throws<NLogConfigurationException>(() =>
166+
new LogFactory().Setup().LoadConfiguration(cfg =>
167+
{
168+
cfg.Configuration.AddRuleForAllLevels(mmt);
169+
})
170+
);
171+
}
172+
173+
[Fact]
174+
public void MailTargetInitialize_WithSmtpAuthenticationModeOAuth2_ThrowsConfigException()
175+
{
176+
var mmt = new MailTarget
177+
{
178+
179+
180+
Subject = "Hello from NLog",
181+
SmtpServer = "server1",
182+
SmtpPort = 27,
183+
SmtpAuthentication = SmtpAuthenticationMode.OAuth2,
184+
};
185+
186+
Assert.Throws<NLogConfigurationException>(() =>
187+
new LogFactory().Setup().LoadConfiguration(cfg =>
188+
{
189+
cfg.Configuration.AddRuleForAllLevels(mmt);
190+
})
191+
);
192+
}
28193
}
29194
}

0 commit comments

Comments
 (0)