xenadmin/XenModel/Actions/Updates/TokenManager.cs
Konstantina Chremmou 02ded9ba71 CP-41474: Added setting on the Options dialog to import a client ID for downloading hotfixes for LCM servers.
Signed-off-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
2023-06-13 14:48:23 +01:00

250 lines
10 KiB
C#

/* Copyright (c) Cloud Software Group, Inc.
*
* Redistribution and use in source and binary forms,
* with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Web.Script.Serialization;
namespace XenAdmin.Actions.Updates
{
public static class TokenManager
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private static readonly int ExpiryInMinutes = 1440; //24 hours
private static object _lock = new object();
private static FileServiceToken _token;
public static void GetToken(IConfigProvider provider)
{
lock (_lock)
{
if (!IsTokenValid())
CreateToken(provider);
}
}
public static string GetDownloadCredential(IConfigProvider configProvider)
{
lock (_lock)
{
if (!IsTokenValid())
CreateToken(configProvider);
return Convert.ToBase64String(Encoding.UTF8.GetBytes(
$"{configProvider.FileServiceUsername}_{_token.session_id}:{_token.token}"));
}
}
public static void InvalidateToken(IConfigProvider configProvider)
{
lock (_lock)
{
if (_token != null)
DeleteToken(configProvider);
}
}
private static void CreateToken(IConfigProvider configProvider)
{
var url = configProvider.GetCustomTokenUrl() ?? InvisibleMessages.TOKEN_API_URL;
var username = configProvider.FileServiceUsername;
var clientId = configProvider.FileServiceClientId;
var credential = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{clientId}"));
var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.Headers.Add("Authorization", $"Basic {credential}");
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "POST";
httpWebRequest.Proxy = configProvider.GetProxyFromSettings(null, false);
using (var requestStream = httpWebRequest.GetRequestStream())
using (var streamWriter = new StreamWriter(requestStream))
streamWriter.Write($"{{\"expiration\": {ExpiryInMinutes}}}");
try
{
using (var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse())
{
if (httpResponse.StatusCode != HttpStatusCode.Created)
throw new WebException();
using (var responseStream = httpResponse.GetResponseStream())
{
if (responseStream == null)
throw new WebException();
log.Info("Account authenticated. Created token.");
using (var streamReader = new StreamReader(responseStream))
{
var json = streamReader.ReadToEnd();
_token = new JavaScriptSerializer().Deserialize(json, typeof(FileServiceToken)) as FileServiceToken;
}
}
}
}
catch (WebException ex)
{
if (!(ex.Response is HttpWebResponse response))
throw new WebException(Messages.FILESERVICE_AUTHENTICATE_ERROR);
switch (response.StatusCode)
{
case HttpStatusCode.BadRequest:
log.Error("Could not authenticate account (400 Bad request). A field provided was invalid");
throw new Exception(string.Format(Messages.STRING_SPACE_STRING,
Messages.FILESERVICE_AUTHENTICATE_ERROR, Messages.FILESERVICE_ERROR_400));
case HttpStatusCode.Unauthorized:
log.Error("Could not authenticate account (401 Unauthorized). Client ID may be invalid or revoked.");
throw new Exception(string.Format(Messages.STRING_SPACE_STRING,
Messages.FILESERVICE_AUTHENTICATE_ERROR, Messages.FILESERVICE_ERROR_401));
case HttpStatusCode.InternalServerError:
log.Error("Could not authenticate account (500 Internal server error). Could not get session token or configuration object was null.");
throw new Exception(string.Format(Messages.STRING_SPACE_STRING,
Messages.FILESERVICE_AUTHENTICATE_ERROR, Messages.FILESERVICE_ERROR_500));
default:
log.Error($"Could not authenticate account (HTTP status code {response.StatusCode})");
throw new WebException(Messages.FILESERVICE_AUTHENTICATE_ERROR);
}
}
}
private static bool IsTokenValid()
{
if (_token == null)
return false;
if (DateTime.TryParse(_token.expires_at, out var expiryDate) &&
expiryDate - DateTime.Now > TimeSpan.Zero)
return true;
return false;
}
private static void DeleteToken(IConfigProvider configProvider)
{
var username = configProvider.FileServiceUsername;
var clientId = configProvider.FileServiceClientId;
var url = configProvider.GetCustomTokenUrl() ?? InvisibleMessages.TOKEN_API_URL;
var credential = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{clientId}"));
var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.Headers.Add("Authorization", $"Basic {credential}");
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "DELETE";
httpWebRequest.Proxy = configProvider.GetProxyFromSettings(null, false);
try
{
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
streamWriter.Write($"{{\"sessionId\": \"{_token.session_id}\"}}");
using (var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse())
{
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
log.Warn($"Could not invalidated session token ({httpResponse.StatusCode}).");
return;
}
using (var responseStream = httpResponse.GetResponseStream())
{
if (responseStream == null)
{
log.Warn("Could not invalidated session token.");
return;
}
using (var streamReader = new StreamReader(responseStream))
{
var json = streamReader.ReadToEnd();
}
}
}
}
catch (WebException wex)
{
if (wex.Response is HttpWebResponse response)
{
switch (response.StatusCode)
{
case HttpStatusCode.BadRequest:
log.Warn("Could not invalidated session token (400 Bad request).");
return;
case HttpStatusCode.Unauthorized:
log.Warn("Could not invalidated session token (401 Unauthorized). Client ID may be invalid or revoked.");
return;
case HttpStatusCode.InternalServerError:
log.Warn("Could not invalidated session token (500 Internal server error).");
return;
}
}
log.Warn("Could not invalidated session token.", wex);
}
catch (Exception e)
{
log.Warn("Could not invalidated session token.", e);
}
finally
{
_token = null;
}
}
}
[Serializable]
public class FileServiceClientId
{
public string username { get; set; }
public string apikey { get; set; }
public string createdDate { get; set; }
}
[Serializable]
public class FileServiceToken
{
public string token { get; set; }
public string token_type { get; set; }
public string session_id { get; set; }
public string issued_at { get; set; }
public string expires_at { get; set; }
}
}