JD2022-TU1/main/tools/services/JD.Oasis.Service/Implementation/CredentialsManager.cs

142 lines
No EOL
4.9 KiB
C#

using System;
using System.IO;
using JD.Oasis.Service.API;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
namespace JD.Oasis.Service.Implementation
{
/// <summary>
/// Will store/retrieve alternative credentials for Oasis Web Service from a local file
/// or in case the file is not yer created, it will ask the caller to provide a set of credentials
/// using the getCredentials function
/// </summary>
public class CredentialsManager : ICredentialsManager
{
private readonly Func<Tuple<string, string>> _getCredentialsFunc;
// Random generated entropy to secure the storage of the credentials on Windows
private static readonly byte[] Entropy =
{165, 55, 145, 89, 198, 64, 173, 97, 65, 167, 166, 17, 61, 186, 161, 51, 84, 219, 177, 211};
private const string LOGIN_FILE_NAME = @"oasiswebservicelogin.json";
private string _loginFilePath;
public string LoginFilePath
{
get
{
if (_loginFilePath == null)
{
// Default is under Ubisoft App Data folder
_loginFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Ubisoft", LOGIN_FILE_NAME);
}
return _loginFilePath;
}
set => _loginFilePath = value;
}
public CredentialsManager(Func<Tuple<string, string>> getCredentialsFunc)
{
_getCredentialsFunc = getCredentialsFunc ?? throw new ArgumentNullException(nameof(getCredentialsFunc));
}
/// <inheritdoc />
public bool UseAltCredentials => GetLocalLogin() != null;
/// <inheritdoc />
public bool TryGetStoredAltCredentials(out NetworkCredential networkCredential)
{
networkCredential = null;
// First we try to see if there is any local alternative login already set
ServiceLogin localLogin = GetLocalLogin();
if (localLogin != null)
{
// A local login is present, we decrypt the password and use those credentials
byte[] pwBytes = ProtectedData.Unprotect(localLogin.Password, Entropy, DataProtectionScope.CurrentUser);
networkCredential = new NetworkCredential(localLogin.Username, Encoding.Unicode.GetString(pwBytes));
return true;
}
return false;
}
/// <inheritdoc />
public bool TryAskAltCredentials(out NetworkCredential networkCredential)
{
networkCredential = null;
// no local login, we run the function to fetch the credentials
Tuple<string, string> credentials = _getCredentialsFunc();
if (credentials != null &&
credentials.Item1 != null && credentials.Item2 != null)
{
networkCredential = new NetworkCredential(credentials.Item1, credentials.Item2);
return true;
}
return false;
}
/// <inheritdoc />
public void StoreAltCredentials(string username, string password)
{
if (string.IsNullOrEmpty(password))
throw new ArgumentException("Value cannot be null or empty.", nameof(password));
if (string.IsNullOrEmpty(username))
throw new ArgumentException("Value cannot be null or empty.", nameof(username));
// Data to protect. Convert a string to a byte[] using Unicode encoding.
byte[] plaintext = Encoding.Unicode.GetBytes(password);
// Encrypt the password using Windows private keys
byte[] ciphertext = ProtectedData.Protect(plaintext, Entropy, DataProtectionScope.CurrentUser);
// serialize ciphertext
string serializedLogin = JsonConvert.SerializeObject(new ServiceLogin {Username = username, Password = ciphertext},
Formatting.Indented);
File.WriteAllText(LoginFilePath, serializedLogin);
}
private ServiceLogin GetLocalLogin()
{
// if file does not exist, there is no local login
if (!File.Exists(LoginFilePath))
{
return null;
}
try
{
ServiceLogin login = JsonConvert.DeserializeObject<ServiceLogin>(File.ReadAllText(LoginFilePath));
return login;
}
catch (Exception)
{
// file is unreadable
return null;
}
}
}
public class ServiceLogin
{
[JsonProperty("username", Required = Required.Always)]
public string Username { get; set; }
[JsonProperty("password", Required = Required.Always)]
public byte[] Password { get; set; }
}
}