1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-05 23:53:21 +00:00
Files
directory-connector/src/Core/Services/TokenService.cs
Kyle Spearrin 28c0509886 null checks
2017-11-30 15:50:05 -05:00

168 lines
5.1 KiB
C#

using Bit.Core.Models;
using Newtonsoft.Json.Linq;
using System;
using System.Text;
namespace Bit.Core.Services
{
public class TokenService
{
private static TokenService _instance;
private static readonly DateTime _epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private string _accessToken;
private dynamic _decodedAccessToken;
private TokenService() { }
public static TokenService Instance
{
get
{
if(_instance == null)
{
_instance = new TokenService();
}
return _instance;
}
}
public string AccessToken
{
get
{
if(_accessToken != null)
{
return _accessToken;
}
var encBytes = SettingsService.Instance.AccessToken;
if(encBytes?.Value != null)
{
_accessToken = Encoding.ASCII.GetString(encBytes.Decrypt());
}
return _accessToken;
}
set
{
_accessToken = value;
if(_accessToken == null)
{
SettingsService.Instance.AccessToken = null;
}
else
{
var bytes = Encoding.ASCII.GetBytes(_accessToken);
SettingsService.Instance.AccessToken = new EncryptedData(bytes);
bytes = null;
}
}
}
public DateTime AccessTokenExpiration
{
get
{
var decoded = DecodeAccessToken();
if(decoded?["exp"] == null)
{
throw new InvalidOperationException("No exp in token.");
}
return _epoc.AddSeconds(Convert.ToDouble(decoded["exp"].Value<long>()));
}
}
public bool AccessTokenExpired => DateTime.UtcNow < AccessTokenExpiration;
public TimeSpan AccessTokenTimeRemaining => AccessTokenExpiration - DateTime.UtcNow;
public bool AccessTokenNeedsRefresh => AccessTokenTimeRemaining.TotalMinutes < 5;
public string AccessTokenUserId => DecodeAccessToken()?["sub"].Value<string>();
public string AccessTokenEmail => DecodeAccessToken()?["email"].Value<string>();
public string AccessTokenName => DecodeAccessToken()?["name"].Value<string>();
public string RefreshToken
{
get
{
var encData = SettingsService.Instance.RefreshToken;
if(encData != null)
{
return Encoding.ASCII.GetString(encData.Decrypt());
}
return null;
}
set
{
if(value == null)
{
SettingsService.Instance.RefreshToken = null;
}
else
{
var bytes = Encoding.ASCII.GetBytes(value);
SettingsService.Instance.RefreshToken = new EncryptedData(bytes);
bytes = null;
}
}
}
public JObject DecodeAccessToken()
{
if(_decodedAccessToken != null)
{
return _decodedAccessToken;
}
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 = Base64UrlDecode(parts[1]);
if(decodedBytes == null || decodedBytes.Length < 1)
{
throw new InvalidOperationException($"{nameof(AccessToken)} must have 3 parts");
}
_decodedAccessToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes, 0, decodedBytes.Length));
return _decodedAccessToken;
}
private static byte[] Base64UrlDecode(string input)
{
var output = input;
// 62nd char of encoding
output = output.Replace('-', '+');
// 63rd char of encoding
output = output.Replace('_', '/');
// Pad with trailing '='s
switch(output.Length % 4)
{
case 0:
// No pad chars in this case
break;
case 2:
// Two pad chars
output += "=="; break;
case 3:
// One pad char
output += "="; break;
default:
throw new InvalidOperationException("Illegal base64url string!");
}
// Standard base64 decoder
return Convert.FromBase64String(output);
}
}
}