Skip to content

Commit

Permalink
Changing UMLS/VSAC API authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
seanmcilvenna committed Feb 23, 2021
1 parent c4a1ce9 commit 65d1eb0
Show file tree
Hide file tree
Showing 22 changed files with 281 additions and 210 deletions.
9 changes: 2 additions & 7 deletions Trifolia.Config/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,9 @@ public static string GoogleAnalyticsGtag
get { return ConfigurationManager.AppSettings["GoogleAnalyticsGtag"]; }
}

public static string UmlsValidateUrl
public static string UMLSTicketGrantingTicketURL
{
get { return ConfigurationManager.AppSettings["UmlsValidateUrl"]; }
}

public static string UmlsLicenseCode
{
get { return ConfigurationManager.AppSettings["UmlsLicenseCode"]; }
get { return ConfigurationManager.AppSettings["UMLSTicketGrantingTicketURL"]; }
}

public static string EncryptionSecret
Expand Down
29 changes: 29 additions & 0 deletions Trifolia.DB/Migrations/202102230058351_umls.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions Trifolia.DB/Migrations/202102230058351_umls.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Trifolia.DB.Migrations
{
using System;
using System.Data.Entity.Migrations;

public partial class umls : DbMigration
{
public override void Up()
{
AddColumn("dbo.user", "umlsApiKey", c => c.String(maxLength: 255));
DropColumn("dbo.user", "umlsUsername");
DropColumn("dbo.user", "umlsPassword");
}

public override void Down()
{
AddColumn("dbo.user", "umlsPassword", c => c.String(maxLength: 255));
AddColumn("dbo.user", "umlsUsername", c => c.String(maxLength: 255));
DropColumn("dbo.user", "umlsApiKey");
}
}
}
126 changes: 126 additions & 0 deletions Trifolia.DB/Migrations/202102230058351_umls.resx

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions Trifolia.DB/Model/user.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,9 @@ public User()
[StringLength(50)]
public string ExternalOrganizationType { get; set; }

[Column("umlsUsername")]
[Column("umlsApiKey")]
[StringLength(255)]
public string UMLSUsername { get; set; }

[Column("umlsPassword")]
[StringLength(255)]
public string UMLSPassword { get; set; }
public string UMLSApiKey { get; set; }

[Column("apiKey")]
[StringLength(255)]
Expand Down
7 changes: 7 additions & 0 deletions Trifolia.DB/Trifolia.DB.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
<Compile Include="Migrations\201807122126363_updateCDASchemaSeedData.Designer.cs">
<DependentUpon>201807122126363_updateCDASchemaSeedData.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\202102230058351_umls.cs" />
<Compile Include="Migrations\202102230058351_umls.Designer.cs">
<DependentUpon>202102230058351_umls.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\Configuration.cs" />
<Compile Include="Model\appsecurable_role.cs" />
<Compile Include="Model\app_securable.cs" />
Expand Down Expand Up @@ -252,6 +256,9 @@
<EmbeddedResource Include="Migrations\201807122126363_updateCDASchemaSeedData.resx">
<DependentUpon>201807122126363_updateCDASchemaSeedData.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Migrations\202102230058351_umls.resx">
<DependentUpon>202102230058351_umls.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Migrations\201703292147411_multipleValueSetIdentifiers.resx">
Expand Down
36 changes: 6 additions & 30 deletions Trifolia.Import/VSAC/VSACImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public VSACImporter(IObjectRepository tdb)
this.tdb = tdb;
}

public bool Authenticate(string username, string password)
public bool Authenticate(string apiKey)
{
string tgt = UmlsHelper.Authenticate(username, password);
string tgt = UmlsHelper.GetTicketGrantingTicket(apiKey);
this.ticketGrantingTicket = tgt;
return !string.IsNullOrEmpty(this.ticketGrantingTicket);
}
Expand All @@ -41,7 +41,10 @@ public bool Authenticate(string username, string password)
/// <param name="oid">The oid of the value set to retrieve. Should not include any prefix (i.e. "urn:oid:")</param>
public bool ImportValueSet(string oid)
{
string serviceTicket = this.GetServiceTicket();
string serviceTicket = UmlsHelper.GetServiceTicket(this.ticketGrantingTicket);

if (string.IsNullOrEmpty(serviceTicket)) return false;

string url = string.Format(SVS_RETRIEVE_VALUE_SET_URL_FORMAT, oid, serviceTicket);
HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
Expand All @@ -58,33 +61,6 @@ public bool ImportValueSet(string oid)
return false;
}

private string GetServiceTicket()
{
string url = string.Format(ST_URL_FORMAT, this.ticketGrantingTicket);
HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);
byte[] rawBody = Encoding.UTF8.GetBytes("service=http://umlsks.nlm.nih.gov");
webRequest.Method = "POST";
webRequest.ContentType = "text/plain";
webRequest.ContentLength = rawBody.Length;

using (var sw = webRequest.GetRequestStream())
{
sw.Write(rawBody, 0, rawBody.Length);
}

HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();

if (response.StatusCode == HttpStatusCode.OK)
{
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
return sr.ReadToEnd();
}
}

return null;
}

private bool ImportRetrieveValueSet(string retrieveValueSetResponse)
{
List<CodeSystem> codeSystems = this.tdb.CodeSystems.ToList();
Expand Down
2 changes: 1 addition & 1 deletion Trifolia.Plugins/Validation/BaseValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ join vs in valueSets on vsm.ValueSetId equals vs.Id

try
{
if (!currentUser.HasValidUmlsLicense())
if (!currentUser.HasValidUMLSApiKey())
{
results.RestrictDownload = true;
results.Messages.Add("This implementation guide contains VSAC content that you do not currently have a license to. <a href=\"/Account/MyProfile\">Update your profile</a> with your UMLS/VSAC credentials to export this implementation guide.");
Expand Down
12 changes: 3 additions & 9 deletions Trifolia.Powershell/GetVSACValueSetCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,14 @@ public class GetVSACValueSetCommand : BaseCommand

[Parameter(
Mandatory = true,
HelpMessage = "The username to authenticate with VSAC"
HelpMessage = "The API Key to authenticate with UMLS/VSAC"
)]
public string VSACUsername { get; set; }

[Parameter(
Mandatory = true,
HelpMessage = "The password to authenticate with VSAC"
)]
public string VSACPassword { get; set; }
public string UMLSApiKey { get; set; }

protected override void ProcessRecord()
{
VSACImporter importer = new VSACImporter(this.tdb);
importer.Authenticate(this.VSACUsername, this.VSACPassword);
importer.Authenticate(this.UMLSApiKey);

if (importer.ImportValueSet(this.Oid))
this.WriteObject("Successfully imported value set");
Expand Down
126 changes: 45 additions & 81 deletions Trifolia.Shared/UmlsHelper.cs
Original file line number Diff line number Diff line change
@@ -1,113 +1,77 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Trifolia.Config;
using Trifolia.Logging;

namespace Trifolia.Shared
{
public class UmlsHelper
{
private const string TGT_URL = "https://vsac.nlm.nih.gov/vsac/ws/Ticket";
private const string TGT_BODY_FORMAT = "username={0}&password={1}";

/// <summary>
/// Authenticates the user with the VSAC using the credentials specified.
/// </summary>
/// <param name="username">The VSAC username</param>
/// <param name="password">The VSAC password</param>
/// <returns>True if authenticated, otherwise false.</returns>
public static string Authenticate(string username, string password)
public static string GetTicketGrantingTicket(string apiKey)
{
HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(TGT_URL);
string body = string.Format(TGT_BODY_FORMAT, username, password);
byte[] rawBody = Encoding.UTF8.GetBytes(body);
webRequest.Method = "POST";
webRequest.ContentType = "text/plain";
webRequest.ContentLength = rawBody.Length;

using (var sw = webRequest.GetRequestStream())
{
sw.Write(rawBody, 0, rawBody.Length);
}
string url = AppSettings.UMLSTicketGrantingTicketURL;
HttpWebRequest tgtRequest = (HttpWebRequest)HttpWebRequest.Create(url);
tgtRequest.Method = "POST";
tgtRequest.ContentType = "application/x-www-form-urlencoded";
tgtRequest.Accept = "application/xml";

try
{
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();

if (response.StatusCode == HttpStatusCode.OK)
using (StreamWriter sw = new StreamWriter(tgtRequest.GetRequestStream()))
{
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
return sr.ReadToEnd();
}
sw.Write("apikey=" + apiKey);
}
}
catch (WebException wex)
{
Log.For(typeof(UmlsHelper)).Error("Error authenticating with UMLS", wex);
}

return null;
}

public static bool ValidateCredentials(string username, string password)
{
string ticketGrantingTicket = Authenticate(username, password);
return !string.IsNullOrEmpty(ticketGrantingTicket);
}
HttpWebResponse tgtResponse = (HttpWebResponse)tgtRequest.GetResponse();

public static bool ValidateLicense(string username, string password)
{
string licenseCode = AppSettings.UmlsLicenseCode;
string[] query = new string[] {
"user=" + Uri.EscapeDataString(username),
"password=" + Uri.EscapeDataString(password),
"licenseCode=" + Uri.EscapeDataString(licenseCode)
};
string url = AppSettings.UmlsValidateUrl + "?" + string.Join("&", query);
HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);
webRequest.Method = "POST";
webRequest.ContentType = "x-www-form-urlencoded";
webRequest.Accept = "application/xml";
if (tgtResponse.StatusCode != HttpStatusCode.Created)
return null;

var response = (HttpWebResponse) webRequest.GetResponse();
string location = tgtResponse.GetResponseHeader("Location");

if (response.StatusCode != HttpStatusCode.OK)
return false;
if (string.IsNullOrEmpty(location) || location.IndexOf("TGT") < 0)
return null;

using (StreamReader sr = new StreamReader(response.GetResponseStream()))
return location.Substring(location.IndexOf("TGT"));
}
catch
{
var responseContent = sr.ReadToEnd();
bool isValid = false;
return null;
}
}

if (responseContent.StartsWith("\"") && responseContent.EndsWith("\""))
responseContent = responseContent.Substring(0, responseContent.Length - 2);
public static string GetServiceTicket(string tgt)
{
HttpWebRequest serviceTicketRequest = (HttpWebRequest) HttpWebRequest.Create("https://utslogin.nlm.nih.gov/cas/v1/tickets/" + tgt);
serviceTicketRequest.Method = "POST";
serviceTicketRequest.ContentType = "application/x-www-form-urlencoded";

try
try
{
using (StreamWriter sw = new StreamWriter(serviceTicketRequest.GetRequestStream()))
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(responseContent);
sw.Write("service=http://umlsks.nlm.nih.gov");
}

if (doc.DocumentElement.Name != "Result" || !Boolean.TryParse(doc.DocumentElement.InnerText, out isValid))
{
Log.For(typeof(UmlsHelper)).Error("Unexpected response from UMLS validation service: {0}", responseContent);
return false;
}
HttpWebResponse stResponse = (HttpWebResponse)serviceTicketRequest.GetResponse();

return isValid;
}
catch (Exception ex)
using (StreamReader sr = new StreamReader(stResponse.GetResponseStream()))
{
Log.For(typeof(UmlsHelper)).Error("Error validation UMLS license for {0}", ex, username);
return false;
return sr.ReadToEnd();
}
}
catch
{
return null;
}
}

public static bool ValidateLicense(string apiKey)
{
string tgt = GetTicketGrantingTicket(apiKey);
if (string.IsNullOrEmpty(tgt)) return false;
string serviceTicket = GetServiceTicket(tgt);
return !string.IsNullOrEmpty(serviceTicket);
}
}
}
27 changes: 5 additions & 22 deletions Trifolia.Shared/UserExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,15 @@ namespace Trifolia.Shared
{
public static class UserExtensions
{
public static bool HasValidUmlsLicense(this User user, string username = null, string password = null)
public static bool HasValidUMLSApiKey(this User user, string apiKey = null)
{
if (string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(user.UMLSUsername))
username = user.UMLSUsername.DecryptStringAES();
if (string.IsNullOrEmpty(apiKey) && !string.IsNullOrEmpty(user.UMLSApiKey))
apiKey = user.UMLSApiKey.DecryptStringAES();

if (string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(user.UMLSPassword))
password = user.UMLSPassword.DecryptStringAES();

if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return false;

return UmlsHelper.ValidateLicense(username, password);
}

public static bool HasValidUmlsCredentials(this User user, string username = null, string password = null)
{
if (string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(user.UMLSUsername))
username = user.UMLSUsername.DecryptStringAES();

if (string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(user.UMLSPassword))
password = user.UMLSPassword.DecryptStringAES();

if (string.IsNullOrEmpty(user.UMLSUsername) || string.IsNullOrEmpty(user.UMLSPassword))
if (string.IsNullOrEmpty(apiKey))
return false;

return UmlsHelper.ValidateCredentials(username, password);
return UmlsHelper.ValidateLicense(apiKey);
}
}
}
Loading

0 comments on commit 65d1eb0

Please sign in to comment.