Skip to content

Commit

Permalink
wip #53
Browse files Browse the repository at this point in the history
* added tests for api calls with auth
* added httpmethods, headers, body in webwatcher config
  • Loading branch information
Yaroslav Husynin committed Mar 24, 2020
1 parent 64539a8 commit aa45e52
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ public class WebWatcherConfigurationModel : IAmConfigurationModelWithResolution
{
[JsonProperty(PropertyName = "associatedIISPool")]
public string AssociatedIISPool { get; set; }

[JsonProperty(PropertyName = "url")]
public Uri Url { get; set; }

[JsonProperty(PropertyName = "urlAlias")]
public string UrlAlias { get; set; }

Expand Down Expand Up @@ -43,5 +43,22 @@ public class WebWatcherConfigurationModel : IAmConfigurationModelWithResolution
public string RestoredMessage { get; set; }
public int MaxRetryCount { get; set; } = 2;
public int SendReminderEmailAfterRetryCount { get; set; } = 120;
public enum HttpCallApiMethod
{
Get = 1,
Post = 2
}

public HttpCallApiMethod Method { get; }

/// <summary>
/// Request Body that may be required for POST request.
/// </summary>
public string Body { get; } = null;

/// <summary>
/// Request headers.
/// </summary>
public IDictionary<string, string> Headers { get; } = null;
}
}
5 changes: 3 additions & 2 deletions Elfo.Wardein.Abstractions/WebWatcher/IAmUrlResponseManager.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System;
using Elfo.Wardein.Abstractions.Configuration.Models;
using System.Threading.Tasks;
using static Elfo.Wardein.Abstractions.Configuration.Models.WebWatcherConfigurationModel;

namespace Elfo.Wardein.Abstractions.WebWatcher
{
public interface IAmUrlResponseManager
{
Task<bool> IsHealthy(bool assertWithStatusCode, string assertWithRegex, Uri url);
Task<bool> IsHealthy(WebWatcherConfigurationModel configuration, HttpCallApiMethod method);
Task RestartPool(string poolName);
}
}
105 changes: 105 additions & 0 deletions Elfo.Wardein.Core.Tests/WebWatcherCallWithApiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using Elfo.Wardein.Abstractions.Configuration.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Win32.SafeHandles;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Security.Principal;
using System.Threading.Tasks;

namespace Elfo.Wardein.Core.Tests
{

[TestClass]
public class WebWatcherCallWithApiClient
{
private WebWatcherConfigurationModel configuration;
private string userNameToImpersonate;
private string domainToImpersonate;
private string userPasswordToImpersonate;

[TestInitialize]
public void Initialize()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();

userNameToImpersonate = configuration["Impersonate:userNameToImpersonate"];
domainToImpersonate = configuration["Impersonate:userDomainToImpersonate"];
userPasswordToImpersonate = configuration["Impersonate:userPasswordToImpersonate"];
}

async Task<bool> IsSuccessStatusCode(HttpClient client)
{
var response = await client.GetAsync(configuration.Url.AbsoluteUri);
return response.IsSuccessStatusCode;
}

[TestMethod]
[TestCategory("ManualTest")]
public async Task IsHealthy()
{
var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true, PreAuthenticate = true, AllowAutoRedirect = true });
client.BaseAddress = new Uri(configuration.Url.AbsoluteUri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = false;
try
{
ImpersonateUser iu = new ImpersonateUser();
iu.RunImpersonated(domainToImpersonate, userNameToImpersonate, userPasswordToImpersonate,
() => IsSuccessStatusCode(client).Wait());
response = await IsSuccessStatusCode(client);
}
catch(Exception ex) {
Console.WriteLine($"There is an error: {ex}");
}

Assert.IsTrue(response);
}
}

[TestClass]
public class ImpersonateUser
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken);

// Test harness.
// If you incorporate this code into a DLL, be sure to demand FullTrust.
[TestMethod]
[TestCategory("ManualTest")]
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public void RunImpersonated(string domainName, string userName, string password, Action action)
{
try
{
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;

// Call LogonUser to obtain a handle to an access token.
SafeAccessTokenHandle safeAccessTokenHandle;
bool returnValue = LogonUser(userName, domainName, password,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeAccessTokenHandle);

if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
WindowsIdentity.RunImpersonated(safeAccessTokenHandle, action);
}
catch (Exception ex)
{
Console.WriteLine("Exception occurred. " + ex.Message);
}
}
}
}
2 changes: 2 additions & 0 deletions Elfo.Wardein.Core/Elfo.Wardein.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.30" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="6.0.4" />
<PackageReference Include="Microsoft.Web.Administration" Version="11.1.0" />
Expand Down
153 changes: 102 additions & 51 deletions Elfo.Wardein.Core/ServiceManager/HttpClientUrlResponseManager.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,77 @@
using Elfo.Wardein.Abstractions.WebWatcher;
using Elfo.Wardein.Core.ServiceManager;
using Elfo.Wardein.Abstractions.Configuration.Models;
using Elfo.Wardein.Abstractions.WebWatcher;
using Microsoft.Extensions.Configuration;
using Microsoft.Win32.SafeHandles;
using NLog;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static Elfo.Wardein.Abstractions.Configuration.Models.WebWatcherConfigurationModel;

namespace Elfo.Wardein.Core.ServiceManager
{
public class HttpClientUrlResponseManager : IAmUrlResponseManager
{
private string userNameToImpersonate;
private string domainToImpersonate;
private string userPasswordToImpersonate;

public async Task<bool> IsHealthy(bool assertWithStatusCode, string assertWithRegex, Uri url)
protected static ILogger log = LogManager.GetCurrentClassLogger();

public HttpClientUrlResponseManager()
{
using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
// TODO support authentication
client.BaseAddress = url;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();

userNameToImpersonate = configuration["Impersonate:userNameToImpersonate"];
domainToImpersonate = configuration["Impersonate:userDomainToImpersonate"];
userPasswordToImpersonate = configuration["Impersonate:userPasswordToImpersonate"];
}

var response = await client.GetAsync(url);


if (!assertWithStatusCode)
public async Task<bool> IsHealthy(WebWatcherConfigurationModel configuration, HttpCallApiMethod method)
{
HttpResponseMessage response = null;
var apiClient = InitializeApiClient(configuration);
ImpersonateUser iu = new ImpersonateUser();
try
{
iu.RunImpersonated(domainToImpersonate, userNameToImpersonate, userPasswordToImpersonate,
async () => response = await apiClient.GetAsync(configuration.Url.AbsoluteUri));
}
catch (UnauthorizedAccessException ex)
{
log.Error($"Exception got while waiting response from {configuration.Url.AbsoluteUri} - {ex}");
throw;
}
if (!configuration.AssertWithStatusCode)
{
if (!string.IsNullOrWhiteSpace(configuration.AssertWithRegex))
{
if (!string.IsNullOrWhiteSpace(assertWithRegex))
{
return await CheckIsMatch(assertWithRegex, response);
}
else
{
return await Task.FromResult(true);
}
return await CheckIsMatch(configuration.AssertWithRegex, response);
}
else
{
if (response.StatusCode != HttpStatusCode.OK)
{
return await Task.FromResult(false);
}
else
{
return await CheckIsMatch(assertWithRegex, response);
}
return await Task.FromResult(true);
}
}
else
{
if (response.StatusCode != HttpStatusCode.OK)
{
return await Task.FromResult(false);
}
else
{
return await CheckIsMatch(configuration.AssertWithRegex, response);
}
}
}
Expand All @@ -70,33 +97,57 @@ private async Task<bool> CheckIsMatch(string assertionRegex, HttpResponseMessage
}
}

public Task RestartPool(string poolName)
public async Task RestartPool(string poolName)
{
new IISPoolManager(poolName).Restart();
return Task.CompletedTask;
await new IISPoolManager(poolName).Restart();
}

//private List<string> AuthCredentials(string username, string password)
//{
// List<string> result = null;
HttpClient InitializeApiClient(WebWatcherConfigurationModel configuration)
{
var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true, PreAuthenticate = true });
client.BaseAddress = new Uri(configuration.Url.AbsoluteUri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

return client;
}

public class ImpersonateUser
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken);

// Test harness.
// If you incorporate this code into a DLL, be sure to demand FullTrust.

[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public void RunImpersonated(string domainName, string userName, string password, Action action)
{
try
{
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;

// CredentialCache.DefaultNetworkCredentials.UserName = username;
// CredentialCache.DefaultNetworkCredentials.Password = password;
// Call LogonUser to obtain a handle to an access token.
SafeAccessTokenHandle safeAccessTokenHandle;
bool returnValue = LogonUser(userName, domainName, password,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeAccessTokenHandle);

// using (var authtHandler = new HttpClientHandler { Credentials = CredentialCache.DefaultNetworkCredentials })
// {
// using (var httpClient = new HttpClient(authtHandler))
// {
// httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("Keep-Alive"));
// HttpResponseMessage message = httpClient.GetAsync("<service URI>").Result;
// if (message.IsSuccessStatusCode)
// {
// var inter = message.Content.ReadAsStringAsync();
// result = JsonConvert.DeserializeObject<List<string>>(inter.Result);
// }
// }
// }
// return result;
//}
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
WindowsIdentity.RunImpersonated(safeAccessTokenHandle, action);
}
catch (Exception ex)
{
Console.WriteLine("Exception occurred. " + ex.Message);
}
}
}
}
}
4 changes: 1 addition & 3 deletions Elfo.Wardein.Watchers/WebWatcher/WebWatcher.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using Elfo.Wardein.Abstractions.Watchers;
using Elfo.Wardein.Abstractions.WebWatcher;
using Elfo.Wardein.Core.ServiceManager;
using Elfo.Wardein.Core;
using Elfo.Wardein.Core.Helpers;
using Elfo.Wardein.Core.NotificationService;
using NLog;
using NLog.Fluent;
using System;
Expand Down Expand Up @@ -63,7 +61,7 @@ internal virtual async Task<bool> RunCheck()
log.Info($"Starting check on {GetLoggingDisplayName}");

var notificationService = ServicesContainer.NotificationService(Config.NotificationType);
var isHealthy = await urlResponseManager.IsHealthy(Config.AssertWithStatusCode, Config.AssertWithRegex, Config.Url);
var isHealthy = await urlResponseManager.IsHealthy(Config, Config.Method);
var currentStatus = await watcherPersistenceService.UpsertCurrentStatus
(
watcherConfigurationId: Config.WatcherConfigurationId,
Expand Down

0 comments on commit aa45e52

Please sign in to comment.