ELMAH - Get SMTP credentials from Azure Application Settings - azure

I've got an Azure Web App using ELMAH to log unhandled exceptions.
When I first deployed it, the web.config had the full SMTP setup defined in it, and ELMAH emailed exceptions:
<system.net>
<mailSettings>
<smtp from="me#mydomain.com">
<network host="smtp.mailprovider.com"
port="123"
userName="myUserName"
password="p#ssw0rd" />
</stmp>
</mailSettings>
</system.net>
The username and password have since been removed from the web.config, and they're now stored as application settings, configured through the Azure Portal.
Most of the emails I send still work fine, as the email code can access these application settings and use them when instantiating the SmtpClient, e.g.:
var userName = WebConfigurationManager.AppSettings["smtp.userName"];
var password = WebConfigurationManager.AppSettings["smtp.password"];
var credentials = new NetworkCredential(userName, password);
using (var smtpClient = new SmtpClient { Credentials = credentials })
{
await smtpClient.SendMailAsync(mailMessage);
}
What's the best way to get ELMAH to use the credentials stored in the application settings?
Options I can see:
There is a page on the wiki explaining how to use ELMAH's ErrorTweetModule to do an HTTP form post with the error details to any URL. The controller receiving the post could then use the stored credentials to email the details on.
The WebBase has a link to an article suggesting you can send emails directly to the recipient's SMTP server without authentication, but it says this may not work if you have DomainKeys set up, which I do.
This answer links to an article about intercepting the Mailing event, to customise the message.

I ended up creating a custom version of Elmah's ErrorMailModule, derived from the standard one, but overriding the SendMail method, based on some advice from Atif Aziz in a discussion on Google Groups.
The only changes required were to create the new module, and switch the Web.Config to use the custom module instead of the standard one.
Module
using System;
using System.Net.Mail;
namespace Test
{
public class ErrorMailModule : Elmah.ErrorMailModule
{
protected override void SendMail(MailMessage mail)
{
if (mail == null) throw new ArgumentNullException(nameof(mail));
// do what you want with the mail
// (in my case this fires up the email service, which
// gets the credentials from the Azure settings)
}
}
}
Web Config Changes
All that's required is to change the two occurrences of Elmah.ErrorLogModule, Elmah to your own module, in this case Test.ErrorMailModule.
So, instead of this...
<system.web>
<httpModules>
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
</httpModules>
</system.web>
<system.webServer>
<modules>
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
</modules>
</system.webServer>
...you should now have this:
<system.web>
<httpModules>
<add name="ErrorMail" type="Test.ErrorMailModule" />
</httpModules>
</system.web>
<system.webServer>
<modules>
<add name="ErrorMail" type="Test.ErrorMailModule" preCondition="managedHandler" />
</modules>
</system.webServer>
You will still need the errorMail section, as Elmah is still responsible for creating the email. Mine looks like this:
<elmah>
<errorMail from="user#domain.com" to="user#domain.com" subject="Custom Email Module"/>
</elmah>

Creating a HTTP request could work, but that should be the solution if everything else doesn't work IMO. Intercepting the Mailing event doesn't work, since you do not have access to the SmtpClient with the credentials in that event.
I've looked at different ways to update the SMTP settings from code. At first I though that I could just get a reference to the smtp section and update the properties, since they all have setter. But the code throw a configuration exception on runtime.
From what I can find, the only way to update the username and password in smtp section, is to read the web.config, update it and write the new version. Here's an example of writing updates to web.config:
var configuration = WebConfigurationManager.OpenWebConfiguration("~");
var section = configuration.GetSection("system.net/mailSettings/smtp") as SmtpSection;
section.Network.UserName = ConfigurationManager.AppSettings["myusername"];
section.Network.Password = ConfigurationManager.AppSettings["mypassword"];
configuration.Save();
The code actually updates the web.config. The code can be run at startup, but that would modify your web.config file locally as well. Another approach would be to run the code as part of a post deployment task with Azure.

Related

Adding new handler to the web.config

I am developing a custom web application and try to change the default file extensions used to serve the webpages, I want to register new handler in the web.config so the user can request the pages with new extension: *.do
I am editing the web.config directly because I want to automate the procedure with powershell next.
.......
According to your description, I suggest you could add below web.config try to use below config setting.
<handlers>
<add name="SampleHandler" verb="*"
path="*.do"
type="SampleHandler, SampleHandlerAssembly"
resourceType="Unspecified" />
</handlers>
More details, you could refer to below article:
https://learn.microsoft.com/en-us/iis/configuration/system.webserver/handlers/add

502 error upload csv file to web api

I have an Angular5 user interface with a file upload function. The user clicks a button and selects a file and the file is sent to the web api (asp.NET Core) method for processing.
This works fine with smaller files, but with larger files the request times out with a 502 error.
I can see the request always timesout at 120 seconds. (NOTE: I am hosting via node in development and via IIS in production).
In the case of large files I need to extend this timeout to a larger value. I've tried to achieve this in a number of ways:
Request Header - Timeout of request in angular code. I used the following code to try to set the timeout header value but it doesn't effect the 120 seconds:
export class AuthTokenInterceptor implements HttpInterceptor {
constructor(private authContext: AuthContext) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authHeaderValue = this.authContext.getAuthenticationHeaderValue(req.url);
if (authHeaderValue) {
const authReq = req.clone({ headers:
req.headers.set('Authorization', authHeaderValue)
.set('Timeout', '100000000000000000') });
return next.handle(authReq);
}
return next.handle(req);
} }
web.config - I've tried setting the httpRuntime timeout value in the web.config file to the following (but still times out at 120 seconds):
Within IIS - I've tried setting the configuration of the "Limits" property in IIS and again, still times out at 120 seconds (and this has no relevance when I'm running through node server).
Has anyone been able to modify this 120 seconds in their Angular(2+) app requests?
Thanks for any pointers in advance!
NOTE: just for completeness, here's my asp.net core, controller method for uploading:
[HttpPost("Upload")]
public async Task<IActionResult> UploadAsync(IFormFile file)
{
// Ensure the file has contents before processing.
if (file == null || file.Length == 0)
throw new ApiException("Csv file should not be null", HttpStatusCode.BadRequest)
.AddApiExceptionResponseDetails(ErrorTypeCode.ValidationError, ErrorCode.BelowMinimumLength, SOURCE);
// Ensure the file is not over the allowed limit.
if (file.Length > (_settings.MaxCsvFileSize * 1024))
throw new ApiException("Max file size exceeded, limit of " + _settings.MaxCsvFileSize + "mb", HttpStatusCode.BadRequest)
.AddApiExceptionResponseDetails(ErrorTypeCode.ValidationError, ErrorCode.ExceedsMaximumLength, SOURCE);
// Ensure the file type is csv and content type is correct for the file.
if (Path.GetExtension(file.FileName) != ".csv" ||
!Constants.CsvAcceptedContentType.Contains(file.ContentType.ToLower(CultureInfo.InvariantCulture)))
throw new ApiException("Csv content only accepted").AddApiExceptionResponseDetails(ErrorTypeCode.ValidationError, ErrorCode.Invalid, SOURCE);
// Read csv content.
var content = await file.ReadCsvAsync<OrderCsvResponseDto>() as CsvProcessedResponseDto<OrderCsvResponseDto>;
await ProcessBulkUpload(content);
// Return information about the csv file.
return Ok(content);
}
Note - when I run the web api via IIS Express then it times out, I've run it using the command host and it doesn't time out - seem's like this may be related to an IIS setting of sorts. The web api doesn't have a web.config file due to the new version of ASP.net Core I'm using but this piece of code doesn't seem to have any bearing on IIS Express when I run through it:
var host = new WebHostBuilder()
.UseStartup<Startup>()
.UseKestrel(o => {
o.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(10);
o.ShutdownTimeout = TimeSpan.FromMinutes(10);
o.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(10);
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseApplicationInsights()
.Build();
I will post this here in case anyone else comes across the same problem I was having. It turns out that running in IIS Express (or hosted IIS) that the config setting overrides whatever you have in code (maybe this is because the newer version of .net Core doesn't have a web.config file in the project - I'm not sure).
Anyway, I worked around this problem by carrying out the following steps:
Open IIS Express in your taskbar
Click on the app you are running (and wish to extend the request timeout for).
Clicking the app shows the config file of the application. Double click to open the config file.
Apply the following setting to the aspNetCore section:
requestTimeout="00:20:00"
Example:
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" forwardWindowsAuthToken="false" requestTimeout="00:20:00" stdoutLogEnabled="false" />
<httpCompression>
<dynamicCompression>
<add mimeType="text/event-stream" enabled="false" />
</dynamicCompression>
</httpCompression>
</system.webServer>
And that's it!
NOTE: I am hosting the app in PaaS ASE - so cant configure IIS directly here. My solution for this was now to add a web.config file to the api project, and apply my setting within it. The build process honours the web.config you've got already instead of generating one on the fly and it will keep the changes needed. In my case, the web.config looked like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" forwardWindowsAuthToken="false" requestTimeout="00:20:00" stdoutLogEnabled="false" />
</system.webServer>
</configuration>
Hopefully this helps others!

CORS problems in WebAPI hosted in IIS

I'm trying to implement an application that uses the same Token Based Authentication mechanism demonstrated in this really awesome example by Taiseer Joudeh.
In my application I kept encountering Cors problems. In some configurations I would get a 500 error on the Preflight (OPTIONS) request for the POST to get the token or I could get the token but then get a 404 error on the preflight request for the GET request to the actual API call with the Bearer token.
One difference was that Taiseer's code was setup to host in IISExpress (or Azure) and mine is hosted on Local IIS (running on Windows 7 at the moment).
On a hunch I tried hosting his API under Local IIS and I found the exact same problem. (500 error on the preflight request for the token and it looks like the actual API will work properly)
From what I've been reading it seems like this may be some conflict between the modules and handlers in IIS and the Cors implementation in WebApi but Taiseer's implementation works when hosted in Azure so perhaps it is a difference in the version of IIS (I'm currently running under Windows 7).
How can I sort out what is causing the problem?
The root of the problem
The Token action is not hosted in a controller but is instead built in somewhere in the lower level plumbing. The only access to the mechanism is through the override method GrantResourceOwnerCredentials() in the class that extends OAuthAuthorizationServerProvider. (In our case is ApplicationOAuthProvider.cs).
GrantResourceOwnerCredentials() does have the context available but it is not called as part of the PreFlight request so you have no way to insert the appropriate PreFlight response headers for CORS.
The solution
We eventually settled on the following solution. I'm not a big fan of it because it forces these headers into every response but at least it works.
The solution was to override Application_PreSendRequestHeaders() method in Global.asax to insert the appropriate headers.
Global.asax.cs
void Application_PreSendRequestHeaders(Object sender, EventArgs e)
{
var origin = Request.Headers.Get("Origin");
var validOrigins = ConfigurationManager.AppSettings["allowedCorsOrigins"].Split(',');
if(validOrigins.Any(o => o == origin))
{
Response.Headers.Set("Access-Control-Allow-Origin", origin);
Response.Headers.Set("Access-Control-Allow-Credentials", "true");
Response.Headers.Set("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, withcredentials, Prefer");
Response.Headers.Set("Access-Control-Expose-Headers", "Claims, *");
Response.Headers.Set("Access-Control-Max-Age", "600");
Response.Headers.Set("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS");
}
}
This requires the following web.config entries:
web.config
<configuration>
<appSettings>
<add key="allowedCorsOrigins" value="http://www.allowedsite1.net,http://localhost:22687" />
<add key="allowedCorsMethods" value="get, post, put, delete, options, batch" />
<add key="allowedCorsHeaders" value="*" />
</appSettings>
...
</configuration>
The reason for the loop to search for the valid origins is that you can't respond with a list of allowed origins...
This solved most of the problems with one exception (If I recall correctly was problems with PUT and DELETE verbs). This required removing the "ExtensionlessUrlHandler-Integrated-4.0" and re-adding it with a path and verb in the handlers section of the web.config.
web.config (2nd change)
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="" />
</handlers>
....
</system.webServer>
Useful links related CORS
Really good description of PreFlight for CORS
Excellent Sample Application using Token Auth
It is not the IdentityServer you are using but it could be the same problem. Regarding to the IdentityServer´s Github page you have to activate RAMMFAR (runAllManagedModulesForAllRequests) for your application when running under the IIS.
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
</modules>
</system.webServer>
I had this same issue, I did everythin as suggested by Mr. Tom hall. But still chrome reported no Access-control-allow-origin header is present.. after inspecting with fidler i realized that my request goes through a proxy server and my proxy server is handling the preflight options request..
So in "internet options" i removed the proxy server and found out that everything is working...!!!

Windows Authentication MVC - 1 IIS_Express server, 2 Apps, 2 Results

Good day all
I'm completely stumped. I am developing an MVC App using MS VSExpress 2013 for Web, and am at the point where I need to add Windows Authentication. Creating a default MVC App using the Wizard and selecting Windows Authentication, it appears to work fine. Lets call the default App App1, my developed App App2.
Monitoring the Context.User object within Watch for both Apps:
App1 returns:
User {System.Security.Principal.WindowsPrincipal}
System.Security.Principal.WindowsIdentity
AuthenticationType "Negotiate" string
IsAuthenticated true bool
Name "MyDomain\\Andrew" string
When using the developed app, the result is as follows: Please note, the returned object is different (App1 = System.Security.Principle.WindowsPrinciple, App2 = System.Web.Security.RolePrinciple.
User {System.Web.Security.RolePrincipal}
Identity {System.Security.Principal.GenericIdentity}
AuthenticationType "" string
IsAuthenticated false bool
Name "" string
HttpContext.Current.Request.LogonUserIdentity.Name "MyDomain\\Andrew"
When switching the developed app Development Server properties to Windows Authentication = Enabled, Anonymous Authentication = Disabled, the result is an immediate:
Server Error in "/" Application.
Resource cannot be found.
Http 404...
Requested URL:/Account/Login
I've checked and compared:
Web.config files and IISExpress\config\applicationhost.config settings for both applications.
My limited knowledge (based on reading all the questions on SO I could find), I'm guessing App2 thinks it is using Forms Authentication, not Windows Authentication. App2 is getting the user information through the
HttpContext.Current.Request.LogonUserIdentity.Name object (found this on SO).
I've added:
<add key="autoFormsAuthentication" value="false" />
to Web.config ... no joy.
Anyone have any idea why the two apps are returning different user objects, and where this can be corrected? Why does App2 not get IsAuthenticated=true from the same IISExpress server as App1?
Thanks
http://www.itorian.com/2013/05/windows-authentication-in-mvc4-with-iis.html
For some strange reason (EDIT Here is the reason-> ASP.NET MVC3 and Windows Auth on IIS keeps redirecting to /Account/Login /EDIT , the wizard generated app1 did not require these two lines within Web.config
<add key="autoFormsAuthentication" value="false" />
<add key="enableSimpleMembership" value="false"/>
After adding the 2nd line, the Context User object returned changed from
System.Security.Principal.GenericIdentity
to
System.Security.Principal.WindowsPrincipal
and all is well.
Also ensure your IISExpress server config file applicationhost.config (within the IISExpress installation folder) contains correct entries as required:
Global Entry:
<windowsAuthentication enabled="true">
<providers>
<add value="Negotiate" />
<add value="NTLM" />
</providers>
</windowsAuthentication>

Azure Websites Dropping Custom Headers

An Azure Website I am working on inspects custom headers of incoming requests to decide what to do with the request internally. The request is being sent/received by the website server with the customer headers:
X-HEADER-1: ...
X-HEADER-2: ...
among other standard and non-standard headers.
I verified this by inspecting the FREB logs and looking at GENERAL_REQUEST_HEADERS, which correctly includes my custom headers.
When the application receives the request, those custom headers are not there. I explicitly check for one of them then throw and dump all available headers in the error message.
I have read around that Application Request Routing module can drop these headers. I tried adding this to the website's web.config but still doesn't work:
<system.webServer>
<rewrite>
<allowedServerVariables>
<add name="HTTP_X_HEADER_1" />
<add name="HTTP_X_HEADER_2" />
</allowedServerVariables>
</rewrite>
</system.webServer>
Any idea how I can whitelist my headers to let ARR/Azure let them through?
Update 1
Here is some more info.
This works locally on my dev box. I set up the site in IIS and point it to the project folder and headers are coming in and processed as expected.
It is an ASP.NET MVC website.
Here is the part of the code that reads the header. Again, this works locally.
public class BaseController : Controller
{
public AppControllerBase(...)
{
}
protected override void Initialize(RequestContext requestContext)
{
var header1Value = requestContext.HttpContext.Request.Headers["X-HEADER-1"];
if (string.IsNullOrEmpty(header1Value))
{
var stringBuilder = new StringBuilder();
// append all headers to stringBuilder
var errorMessage = string.Format("SiteId header is not set. Headers: {0}", stringBuilder);
throw new HttpRequestException(errorMessage);
}
base.Initialize(requestContext);
}
...
}
Update 2
I just deployed the same app as an azure cloud service and it worked well. The headers were received and the app read them successfully. Something with web apps is not letting those headers through.
The answer that worked for me was in the comments. Credit goes to #Tarek Ayna.
The custom headers are transmitted when you set X-LiveUpgrade to 0.
For example:
<httpProtocol>
<customHeaders>
<add name="X-LiveUpgrade" value="0" />**
<!-- Prevent iframes -->
<add name="X-Frame-Options" value="SAMEORIGIN" />
<add name="X-XSS-Protection" value="1" />
</customHeaders>
</httpProtocol>
One possibility is to disable ARR if your services are stateless... to do that:
(Inside web.config)
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Arr-Disable-Session-Affinity" value="True" />
</customHeaders>
</httpProtocol>
</system.webServer>

Resources