Files
server/src/Core/Services/Implementations/BaseIdentityClientService.cs

196 lines
6.3 KiB
C#
Raw Normal View History

using System.Collections.Generic;
using System.Threading.Tasks;
using System.Net.Http;
using Newtonsoft.Json;
using System.Text;
using System;
using Newtonsoft.Json.Linq;
using Bit.Core.Utilities;
using System.Net;
using System.Net.Http.Headers;
2017-08-14 13:06:44 -04:00
using Microsoft.Extensions.Logging;
namespace Bit.Core.Services
{
2018-08-15 18:43:26 -04:00
public abstract class BaseIdentityClientService
{
2018-08-15 18:43:26 -04:00
private readonly string _identityScope;
private readonly string _identityClientId;
private readonly string _identityClientSecret;
private readonly ILogger<BaseIdentityClientService> _logger;
private dynamic _decodedToken;
private DateTime? _nextAuthAttempt = null;
2018-08-15 18:43:26 -04:00
public BaseIdentityClientService(
string baseClientServerUri,
string baseIdentityServerUri,
string identityScope,
string identityClientId,
string identityClientSecret,
ILogger<BaseIdentityClientService> logger)
{
2018-08-15 18:43:26 -04:00
_identityScope = identityScope;
_identityClientId = identityClientId;
_identityClientSecret = identityClientSecret;
2017-08-14 13:06:44 -04:00
_logger = logger;
2018-08-15 18:43:26 -04:00
Client = new HttpClient
{
2018-08-15 18:43:26 -04:00
BaseAddress = new Uri(baseClientServerUri)
};
2018-08-15 18:43:26 -04:00
Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
IdentityClient = new HttpClient
{
2018-08-15 18:43:26 -04:00
BaseAddress = new Uri(baseIdentityServerUri)
};
IdentityClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
2018-08-15 18:43:26 -04:00
protected HttpClient Client { get; private set; }
protected HttpClient IdentityClient { get; private set; }
protected string AccessToken { get; private set; }
2018-08-16 12:05:01 -04:00
protected async Task SendAsync(HttpMethod method, string path, object requestModel = null)
{
var tokenStateResponse = await HandleTokenStateAsync();
if(!tokenStateResponse)
{
return;
}
var message = new TokenHttpRequestMessage(requestModel, AccessToken)
{
Method = method,
RequestUri = new Uri(string.Concat(Client.BaseAddress, path))
};
try
{
await Client.SendAsync(message);
}
catch(Exception e)
{
_logger.LogError(12334, e, "Failed to send to {0}.", message.RequestUri.ToString());
}
}
protected async Task<bool> HandleTokenStateAsync()
{
if(_nextAuthAttempt.HasValue && DateTime.UtcNow > _nextAuthAttempt.Value)
{
return false;
}
_nextAuthAttempt = null;
if(!string.IsNullOrWhiteSpace(AccessToken) && !TokenExpired())
{
return true;
}
var requestMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
2018-08-21 14:32:09 -04:00
RequestUri = new Uri(string.Concat(IdentityClient.BaseAddress, "connect/token")),
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "grant_type", "client_credentials" },
2018-08-15 18:43:26 -04:00
{ "scope", _identityScope },
{ "client_id", _identityClientId },
{ "client_secret", _identityClientSecret }
})
};
2017-08-14 13:06:44 -04:00
HttpResponseMessage response = null;
try
{
response = await IdentityClient.SendAsync(requestMessage);
}
catch(Exception e)
{
2018-08-15 18:43:26 -04:00
_logger.LogError(12339, e, "Unable to authenticate with identity server.");
2017-08-14 13:06:44 -04:00
}
2017-08-15 14:48:56 -04:00
if(response == null)
{
return false;
}
if(!response.IsSuccessStatusCode)
{
if(response.StatusCode == HttpStatusCode.BadRequest)
{
_nextAuthAttempt = DateTime.UtcNow.AddDays(1);
}
return false;
}
var responseContent = await response.Content.ReadAsStringAsync();
dynamic tokenResponse = JsonConvert.DeserializeObject(responseContent);
AccessToken = (string)tokenResponse.access_token;
return true;
}
protected class TokenHttpRequestMessage : HttpRequestMessage
{
public TokenHttpRequestMessage(string token)
{
Headers.Add("Authorization", $"Bearer {token}");
}
public TokenHttpRequestMessage(object requestObject, string token)
: this(token)
{
2018-08-16 12:05:01 -04:00
if(requestObject != null)
{
var stringContent = JsonConvert.SerializeObject(requestObject);
Content = new StringContent(stringContent, Encoding.UTF8, "application/json");
}
}
}
protected bool TokenExpired()
{
var decoded = DecodeToken();
var exp = decoded?["exp"];
if(exp == null)
{
throw new InvalidOperationException("No exp in token.");
}
2017-08-11 22:55:25 -04:00
var expiration = CoreHelpers.FromEpocSeconds(exp.Value<long>());
return DateTime.UtcNow < expiration;
}
protected JObject DecodeToken()
{
if(_decodedToken != null)
{
return _decodedToken;
}
if(AccessToken == null)
{
throw new InvalidOperationException($"{nameof(AccessToken)} not found.");
}
var parts = AccessToken.Split('.');
if(parts.Length != 3)
{
throw new InvalidOperationException($"{nameof(AccessToken)} must have 3 parts");
}
var decodedBytes = CoreHelpers.Base64UrlDecode(parts[1]);
if(decodedBytes == null || decodedBytes.Length < 1)
{
throw new InvalidOperationException($"{nameof(AccessToken)} must have 3 parts");
}
_decodedToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes, 0, decodedBytes.Length));
return _decodedToken;
}
}
}