Skip to content

Commit

Permalink
feat: add support for OAuth tokens.
Browse files Browse the repository at this point in the history
  • Loading branch information
lucianaparaschivei committed Aug 31, 2021
1 parent cb9528e commit 980f00d
Show file tree
Hide file tree
Showing 14 changed files with 440 additions and 136 deletions.
211 changes: 149 additions & 62 deletions src/NLog.Targets.ElasticSearch/ElasticSearchTarget.cs

Large diffs are not rendered by default.

132 changes: 132 additions & 0 deletions src/NLog.Targets.ElasticSearch/ElasticsearchTokenProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using Newtonsoft.Json;
using NLog.Common;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

namespace NLog.Targets.ElasticSearch
{
public sealed class ElasticsearchTokenProvider : IElasticsearchTokenProvider
{
private const string DefaultContentType = "application/json";
private readonly Uri _baseAddress;
private readonly string _username;
private readonly string _password;

public ElasticsearchTokenProvider(Uri baseAddress, string username, string password)
{
_baseAddress = baseAddress;
_password = password;
_username = username;
}

public OAuthAccessToken GetToken()
{
var tokenRequest = new
{
grant_type = "password",
username = _username,
password = _password,
};

using var stringContent = new StringContent(
JsonConvert.SerializeObject(tokenRequest),
Encoding.UTF8,
DefaultContentType);

OAuthAccessToken token = null;
try
{
token = DoRequest<OAuthAccessToken>(stringContent, HttpMethod.Post);
if (token != null)
{
token.GeneratedTime = DateTime.UtcNow.Ticks;
}
}
catch (Exception ex)
{
InternalLogger.Error(ex, "ElasticSearch: Error generating the token.");
}

return token;
}

public OAuthAccessToken RefreshToken(OAuthAccessToken currentToken)
{
var refreshToken = new
{
grant_type = "refresh_token",
refresh_token = currentToken?.RefreshToken,
};

using var stringContent = new StringContent(
JsonConvert.SerializeObject(refreshToken),
Encoding.UTF8,
DefaultContentType);

OAuthAccessToken token;
try
{
token = DoRequest<OAuthAccessToken>(stringContent, HttpMethod.Post, currentToken?.AccessToken);
if (token != null)
{
token.GeneratedTime = DateTime.UtcNow.Ticks;
}
}
catch (Exception ex)
{
InternalLogger.Error(ex, "ElasticSearch: Error refreshing the token. Trying to generate new token.");

token = GetToken();
}

return token;
}

private T DoRequest<T>(HttpContent content, HttpMethod httpMethod, string bearerToken = null)
{
using var httpClient = new HttpClient { BaseAddress = _baseAddress };
httpClient.DefaultRequestHeaders.Accept.ParseAdd(DefaultContentType);
using var request = new HttpRequestMessage
{
Method = httpMethod,
RequestUri = new Uri($"/_security/oauth2/token", UriKind.Relative),
Content = content,
};

if (!string.IsNullOrWhiteSpace(bearerToken))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
}
else
{
if (!string.IsNullOrWhiteSpace(_username))
{
var encodedCredential = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_username}:{_password}"));

request.Headers.Authorization = new AuthenticationHeaderValue("Basic", encodedCredential);
}
else
{
InternalLogger.Error("ElasticSearch: For token generation a username must be configured.");
return default;
}
}

using var response = httpClient.SendAsync(request).GetAwaiter().GetResult();

if (!response.IsSuccessStatusCode)
{
var responseContent = response.Content.ReadAsStringAsync();

InternalLogger.Error($"ElasticSearch: error trying to refresh token. Call status: {response.StatusCode}. Response: {responseContent}");
return default;
}

var token = JsonConvert.DeserializeObject<T>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());

return token;
}
}
}
2 changes: 2 additions & 0 deletions src/NLog.Targets.ElasticSearch/ExceptionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ internal static class ExceptionExtensions
public static Exception FlattenToActualException(this Exception exception)
{
if (!(exception is AggregateException aggregateException))
{
return exception;
}

var flattenException = aggregateException.Flatten();
if (flattenException.InnerExceptions.Count == 1)
Expand Down
13 changes: 10 additions & 3 deletions src/NLog.Targets.ElasticSearch/ExpandoObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;

namespace NLog.Targets.ElasticSearch
{
internal static class ExpandoObjectExtensions
{
/// <summary>
/// Replaces dot ('.') character in Keys with an underscore ('_')
/// Replaces dot ('.') character in Keys with an underscore ('_')
/// </summary>
/// <returns>ExpandoObject</returns>
public static ExpandoObject ReplaceDotInKeys(this ExpandoObject obj, bool alwaysCloneObject = true)
Expand All @@ -19,15 +18,21 @@ public static ExpandoObject ReplaceDotInKeys(this ExpandoObject obj, bool always
{
case null:
if (clone == null)
{
return obj.ReplaceDotInKeys();
}

break;
case ExpandoObject expandoObject:
if (clone == null)
{
return obj.ReplaceDotInKeys();
}

((IDictionary<string, object>)clone)[item.Key.Replace('.', '_')] = expandoObject.ReplaceDotInKeys();
break;
default:
if (item.Key.Contains('.'))
if (item.Key.Contains("."))
{
if (clone == null)
return obj.ReplaceDotInKeys();
Expand All @@ -37,9 +42,11 @@ public static ExpandoObject ReplaceDotInKeys(this ExpandoObject obj, bool always
{
((IDictionary<string, object>)clone)[item.Key] = item.Value;
}

break;
}
}

return clone ?? obj;
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/NLog.Targets.ElasticSearch/Field.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using NLog.Config;
using NLog.Config;
using NLog.Layouts;
using System;

namespace NLog.Targets.ElasticSearch
{
Expand All @@ -20,7 +20,7 @@ public class Field
/// Value with NLog <see cref="NLog.Layouts.Layout"/> rendering support
/// </summary>
[RequiredParameter]
public Layout Layout { get; set; }
public Layout Layout { get; set; }

/// <summary>
/// Custom type conversion from default string to other type
Expand Down
9 changes: 6 additions & 3 deletions src/NLog.Targets.ElasticSearch/FlatObjectContractResolver.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Reflection;

namespace NLog.Targets.ElasticSearch
{
Expand All @@ -16,7 +16,10 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ
{
var jsonProperty = base.CreateProperty(member, memberSerialization);
if (jsonProperty.Readable && !IsSimpleType(jsonProperty.PropertyType))
{
jsonProperty.Converter = _flatObjectConverter;
}

return jsonProperty;
}

Expand Down
9 changes: 4 additions & 5 deletions src/NLog.Targets.ElasticSearch/IElasticSearchTarget.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using Elasticsearch.Net;
using NLog.Layouts;
using System;
using System.Collections.Generic;
using System.Net;
using Elasticsearch.Net;
using NLog.Layouts;

namespace NLog.Targets.ElasticSearch
{
Expand All @@ -13,7 +13,6 @@ public interface IElasticSearchTarget
{
/// <summary>
/// Gets or sets a connection string name to retrieve the Uri from.
///
/// Use as an alternative to Uri
/// </summary>
string ConnectionStringName { get; set; }
Expand All @@ -33,7 +32,7 @@ public interface IElasticSearchTarget
/// <summary>
/// Set it to true if ElasticSearch uses BasicAuth
/// </summary>
[Obsolete]
[Obsolete("Deprecated. Authentication is done with BasicAuth if field Username is not empty.")]
bool RequireAuth { get; set; }

/// <summary>
Expand Down
9 changes: 9 additions & 0 deletions src/NLog.Targets.ElasticSearch/IElasticsearchTokenProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace NLog.Targets.ElasticSearch
{
public interface IElasticsearchTokenProvider
{
OAuthAccessToken GetToken();

OAuthAccessToken RefreshToken(OAuthAccessToken currentToken);
}
}
10 changes: 5 additions & 5 deletions src/NLog.Targets.ElasticSearch/JsonToStringConverter.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json;
using System;

namespace NLog.Targets.ElasticSearch
{
internal sealed class JsonToStringConverter : JsonConverter
{
private readonly Type _type;

/// <inheritdoc />
public override bool CanRead { get; } = false;

public JsonToStringConverter(Type type)
{
_type = type;
}

/// <inheritdoc />
public override bool CanRead { get; } = false;

/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
<DelaySign>false</DelaySign>
<AssemblyOriginatorKeyFile>NLog.Targets.ElasticSearch.snk</AssemblyOriginatorKeyFile>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>9</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Elasticsearch.Net" Version="7.13.2" />
<PackageReference Include="NEST" Version="7.13.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.6.8" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
</Project>
54 changes: 54 additions & 0 deletions src/NLog.Targets.ElasticSearch/OAuthAccessToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Newtonsoft.Json;

namespace NLog.Targets.ElasticSearch
{
public class OAuthAccessToken
{
[JsonProperty(PropertyName = "access_token")]
public string AccessToken { get; set; }

[JsonProperty(PropertyName = "expires_in")]
public long ExpiresIn { get; set; }

public string Scope { get; set; }

public string Type { get; set; }

[JsonProperty(PropertyName = "refresh_token")]
public string RefreshToken { get; set; }

public long GeneratedTime { get; set; }

public override int GetHashCode()
{
unchecked
{
var hash = 17;
hash = (hash * 23) + AccessToken?.GetHashCode() ?? 0;
hash = (hash * 23) + RefreshToken?.GetHashCode() ?? 0;
return hash;
}
}

public override bool Equals(object obj)
{
if (obj is null)
{
return false;
}

if (ReferenceEquals(this, obj))
{
return true;
}

if (obj.GetType() != GetType())
{
return false;
}

var objTyped = (OAuthAccessToken)obj;
return this.AccessToken.Equals(objTyped.AccessToken) && this.RefreshToken.Equals(objTyped.RefreshToken);
}
}
}
Loading

0 comments on commit 980f00d

Please sign in to comment.