Author: pabramowitsch
Date: Tue Nov 17 15:10:04 2020
New Revision: 1883539
URL: http://svn.apache.org/viewvc?rev=1883539&view=rev
Log:
Ctakes Jira CT-545 UMLS Authentication
Modified:
ctakes/trunk/ctakes-dictionary-lookup-fast/src/main/java/org/apache/ctakes/dictionary/lookup2/util/UmlsUserApprover.java
Modified:
ctakes/trunk/ctakes-dictionary-lookup-fast/src/main/java/org/apache/ctakes/dictionary/lookup2/util/UmlsUserApprover.java
URL:
http://svn.apache.org/viewvc/ctakes/trunk/ctakes-dictionary-lookup-fast/src/main/java/org/apache/ctakes/dictionary/lookup2/util/UmlsUserApprover.java?rev=1883539&r1=1883538&r2=1883539&view=diff
==============================================================================
---
ctakes/trunk/ctakes-dictionary-lookup-fast/src/main/java/org/apache/ctakes/dictionary/lookup2/util/UmlsUserApprover.java
(original)
+++
ctakes/trunk/ctakes-dictionary-lookup-fast/src/main/java/org/apache/ctakes/dictionary/lookup2/util/UmlsUserApprover.java
Tue Nov 17 15:10:04 2020
@@ -25,6 +25,7 @@ import org.apache.log4j.Logger;
import org.apache.uima.UimaContext;
import java.io.*;
+import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
@@ -32,168 +33,310 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;
-
/**
* Used to validate UMLS license / user.
* <p/>
- * TODO Authentication before download would be nice, or perhaps an encrypted
download
- * Author: SPF
- * Affiliation: CHIP-NLP
- * Date: 2/19/14
+ * TODO Authentication before download would be nice, or perhaps an encrypted
+ * download Author: SPF Affiliation: CHIP-NLP Date: 2/19/14
+ *
+ * UPDATED to use the API_KEY based authentication scheme of the UMLS
+ * For maximum compatibility with existing configurations we permit
+ * umls_user = "umls_api_key" and umls_password = "<APIKEY>" settings
+ * or simply set the ctakes.umls_apikey system property
+ * pabramowitsch (11/2020)
*/
public enum UmlsUserApprover {
- INSTANCE;
+ INSTANCE;
- static public UmlsUserApprover getInstance() {
- return INSTANCE;
- }
-
- // cli, matches new
- static private final String USER_CLI = "--user";
- static private final String PASS_CLI = "--pass";
-
- // properties, matches new
- public final static String URL_PARAM = "umlsUrl";
- public final static String VENDOR_PARAM = "umlsVendor";
- public final static String USER_PARAM = "umlsUser";
- public final static String PASS_PARAM = "umlsPass";
-
- static final private Logger LOGGER = Logger.getLogger( "UmlsUserApprover" );
-
- static final private String CHANGEME = "CHANGEME";
- static final private String CHANGE_ME = "CHANGE_ME";
-
- // cache of valid users
- static private final Collection<String> _validUsers = new ArrayList<>();
-
- /**
- * validate the UMLS license / user
- *
- * @param uimaContext contains information about the UMLS license / user
- * @param properties -
- * @return true if the server at umlsaddr approves of the vendor, user,
password combination
- */
- public boolean isValidUMLSUser( final UimaContext uimaContext, final
Properties properties ) {
- String umlsUrl = EnvironmentVariable.getEnv(
UmlsEnvironmentConfiguration.URL.toString(), uimaContext );
- if ( umlsUrl == null || umlsUrl.equals( EnvironmentVariable.NOT_PRESENT
) ) {
- umlsUrl = properties.getProperty( URL_PARAM );
- }
- String vendor = EnvironmentVariable.getEnv(
UmlsEnvironmentConfiguration.VENDOR.toString(), uimaContext );
- if ( vendor == null || vendor.equals( EnvironmentVariable.NOT_PRESENT )
) {
- vendor = properties.getProperty( VENDOR_PARAM );
- }
- String user = EnvironmentVariable.getEnv(
UmlsEnvironmentConfiguration.USER.toString(), uimaContext );
- if ( user == null || user.equals( EnvironmentVariable.NOT_PRESENT ) ||
user.equals( CHANGEME ) || user.equals( CHANGE_ME ) ) {
- user = EnvironmentVariable.getEnv( USER_PARAM, uimaContext );
- if ( user == null || user.equals( EnvironmentVariable.NOT_PRESENT )
|| user.equals( CHANGEME ) || user.equals( CHANGE_ME ) ) {
- user = properties.getProperty( USER_PARAM );
- }
- }
- String pass = EnvironmentVariable.getEnv(
UmlsEnvironmentConfiguration.PASSWORD.toString(), uimaContext );
- if ( pass == null || pass.equals( EnvironmentVariable.NOT_PRESENT ) ||
pass.equals( CHANGEME ) || pass.equals( CHANGE_ME ) ) {
- pass = EnvironmentVariable.getEnv( PASS_PARAM, uimaContext );
- if ( pass == null || pass.equals( EnvironmentVariable.NOT_PRESENT )
|| pass.equals( CHANGEME ) || pass.equals( CHANGE_ME ) ) {
- pass = properties.getProperty( PASS_PARAM );
- }
- }
- return isValidUMLSUser( umlsUrl, vendor, user, pass );
- }
-
- /**
- * validate the UMLS license / user
- *
- * @param umlsUrl -
- * @param vendor -
- * @param user -
- * @param pass -
- * @return true if the server at umlsaddr approves of the vendor, user,
password combination
- */
- public boolean isValidUMLSUser( final String umlsUrl, final String vendor,
- final String user, final String pass ) {
- if ( user == null || user.trim().isEmpty() ) {
- LOGGER.error( "No UMLS username specified." );
- logCheckUser();
- return false;
- }
- if ( pass == null || pass.trim().isEmpty() ) {
- LOGGER.error( "No UMLS username specified." );
- logCheckPass();
- return false;
- }
- final String cacheCode = umlsUrl + vendor + user + pass;
- if ( _validUsers.contains( cacheCode ) ) {
- return true;
- }
- // Potentially someone could have a user ID of CHANGEME or a password of
CHANGEME but don't allow those
- // to make it easy for us to detect that the user or password was not
set correctly.
- if ( user.equals( CHANGEME ) || user.equals( CHANGE_ME ) ) {
- LOGGER.error( " User " + user + " not allowed. It is a placeholder
reminder." );
- logCheckUser();
- return false;
- }
- if ( pass.equals( CHANGEME ) || pass.equals( CHANGE_ME ) ) {
- LOGGER.error( " Password " + pass + " not allowed. It is a
placeholder reminder." );
- logCheckPass();
- return false;
- }
-
- String data;
- try {
- data = URLEncoder.encode( "licenseCode", "UTF-8" ) + "=" +
URLEncoder.encode( vendor, "UTF-8" );
- data += "&" + URLEncoder.encode( "user", "UTF-8" ) + "=" +
URLEncoder.encode( user, "UTF-8" );
- data += "&" + URLEncoder.encode( "password", "UTF-8" ) + "=" +
URLEncoder.encode( pass, "UTF-8" );
- } catch ( UnsupportedEncodingException unseE ) {
- LOGGER.error( "Could not encode URL for " + user + " with vendor
license " + vendor );
- return false;
- }
-
- try ( DotLogger dotter = new DotLogger() ) {
- LOGGER.info( "Checking UMLS Account at " + umlsUrl + ":" );
- final URL url = new URL( umlsUrl );
- final URLConnection connection = url.openConnection();
- connection.setDoOutput( true );
- final OutputStreamWriter writer = new OutputStreamWriter(
connection.getOutputStream() );
- writer.write( data );
- writer.flush();
- boolean isValidUser = false;
- final BufferedReader reader = new BufferedReader( new
InputStreamReader( connection.getInputStream() ) );
- String line;
- while ( (line = reader.readLine()) != null ) {
- final String trimline = line.trim();
- if ( trimline.isEmpty() ) {
- break;
- }
- isValidUser = trimline.equalsIgnoreCase( "<Result>true</Result>" )
- || trimline.equalsIgnoreCase( "<?xml version='1.0'
encoding='UTF-8'?><Result>true</Result>" );
- }
- writer.close();
- reader.close();
- if ( isValidUser ) {
- LOGGER.info( " UMLS Account has been validated" );
- _validUsers.add( cacheCode );
- } else {
- LOGGER.error( " UMLS Account at " + umlsUrl + " is not valid." );
- logCheckUser();
- logCheckPass();
- }
- return isValidUser;
- } catch ( IOException ioE ) {
- LOGGER.error( ioE.getMessage() );
- return false;
- }
- }
-
- static private String createLogMessage(String cliOption, String property,
UmlsEnvironmentConfiguration envConfig) {
- return String.format(" Verify that you are setting command-line option
%s, or ctakes property %s, or environment variable %s properly.",
- cliOption, property, envConfig);
- }
-
- static private void logCheckUser() {
- LOGGER.error( createLogMessage(USER_CLI, USER_PARAM,
UmlsEnvironmentConfiguration.USER) );
- }
-
- static private void logCheckPass() {
- LOGGER.error( createLogMessage(PASS_CLI, PASS_PARAM,
UmlsEnvironmentConfiguration.PASSWORD) );
- }
+ static public UmlsUserApprover getInstance() {
+ return INSTANCE;
+ }
+
+ // cli, matches new
+ static private final String USER_CLI = "--user";
+ static private final String PASS_CLI = "--pass";
+
+ // properties, matches new
+ public final static String URL_PARAM = "umlsUrl";
+ public final static String VENDOR_PARAM = "umlsVendor";
+ public final static String USER_PARAM = "umlsUser";
+ public final static String PASS_PARAM = "umlsPass";
+ public final static String API_KEY_LABEL = "umls_api_key";
+ public final static String API_KEY_PROP = "ctakes.umls_apikey";
+
+ static final private Logger LOGGER =
Logger.getLogger("UmlsUserApprover");
+
+ static final private String CHANGEME = "CHANGEME";
+ static final private String CHANGE_ME = "CHANGE_ME";
+ // forget about copies of this URL sprinkled around the other libraries
+ static final private String UTS_APIKEY_URL =
"https://utslogin.nlm.nih.gov/cas/v1/api-key";
+
+
+ // cache of valid users
+ static private final Collection<String> _validUsers = new
ArrayList<String>();
+
+ /**
+ * validate the UMLS license / user
+ *
+ * @param uimaContext
+ * contains information about the UMLS license / user
+ * @param properties
+ * possibly containing the attribs we need
+ * If not, we will look in the environment and sysprops.
+ * @return true if the server at umlsaddr approves of the vendor, user,
+ * password combination
+ */
+ public boolean isValidUMLSUser(final UimaContext uimaContext,
+ final Properties properties) {
+
+ String apiUrl = getUrl(properties);
+ String umlsApiKey = getSingleApiProp(properties, uimaContext);
+
+ if (umlsApiKey == null) {
+ // emulate U&P style
+ String user = getUser(uimaContext, properties);
+ if (user == null || !user.equals(API_KEY_LABEL)) {
+ LOGGER.error("USER AND PASSWORD MUST BE
umls_api_key and your <umls_api_key>");
+ return false;
+ }
+
+ String pass = getPassOrKey(uimaContext, properties);
+ if (pass == null || pass.indexOf("CHANGE") != -1 ||
pass.length() <= 24) {
+ LOGGER.error("Upgrade to a UMLS API KEY.
Password no longer accepted");
+ return false;
+ }
+ return isValidUMLSUser(apiUrl, pass);
+ }
+ return isValidUMLSUser(apiUrl, umlsApiKey);
+ }
+
+ /**
+ * See if the user has simply supplied the API key using its own
property name
+ * @param properties
+ * @param uimaContext
+ * @return
+ */
+ private String getSingleApiProp(final Properties properties,
UimaContext uimaContext) {
+ String umlsApiKey = EnvironmentVariable.getEnv(API_KEY_PROP,
uimaContext);
+ LOGGER.debug("The apikey was " + umlsApiKey );
+ return umlsApiKey;
+ }
+
+ private String getUrl() {
+ // get explicitly from the JVM in case any component
+ // still has the old URL mentioned
+ return getUrl(System.getProperties());
+ }
+
+ private String getUrl(final Properties properties) {
+ String where = "environment";
+ String umlsUrl = EnvironmentVariable.getEnv(
+ UmlsEnvironmentConfiguration.URL.toString());
+ if (umlsUrl == null ||
umlsUrl.equals(EnvironmentVariable.NOT_PRESENT)) {
+ umlsUrl = properties.getProperty(URL_PARAM);
+ if (umlsUrl != null) {
+ where = "properties";
+ }
+ }
+ if (umlsUrl == null) {
+ umlsUrl = UTS_APIKEY_URL;
+ where = "default";
+ } else {
+ LOGGER.warn("Using alternate umlsURL found via: " +
where);
+ }
+ LOGGER.debug("umlsUrl found via :" + where);
+ return umlsUrl;
+ }
+
+ private String getPassOrKey(final UimaContext uimaContext,
+ final Properties properties) {
+ String where = "environment";
+ String pass = EnvironmentVariable.getEnv(
+
UmlsEnvironmentConfiguration.PASSWORD.toString(),
+ uimaContext);
+ if (pass == null ||
pass.equals(EnvironmentVariable.NOT_PRESENT)
+ || pass.indexOf("CHANGE") != -1) {
+ pass = EnvironmentVariable.getEnv(PASS_PARAM,
uimaContext);
+ if (pass == null
+ ||
pass.equals(EnvironmentVariable.NOT_PRESENT)
+ || pass.indexOf("CHANGE") != -1) {
+ pass = properties.getProperty(PASS_PARAM);
+ where = "properties";
+ }
+ }
+ if (pass != null)
+ LOGGER.debug("ApiKey value found via: " + where);
+ return pass;
+ }
+
+ private String getUser(final UimaContext uimaContext,
+ final Properties properties) {
+ String where = "environment";
+ String user = EnvironmentVariable.getEnv(
+ UmlsEnvironmentConfiguration.USER.toString(),
uimaContext);
+ if (user == null || user.equals(EnvironmentVariable.NOT_PRESENT)
+ || user.equals(CHANGEME) ||
user.equals(CHANGE_ME)) {
+ user = EnvironmentVariable.getEnv(USER_PARAM,
uimaContext);
+ if (user == null
+ ||
user.equals(EnvironmentVariable.NOT_PRESENT)
+ || user.indexOf("CHANGE") != -1) {
+ user = properties.getProperty(USER_PARAM);
+ where = "properties";
+ }
+ }
+ if (user != null)
+ LOGGER.debug("ApiKey Label found via: " + where);
+ return user;
+ }
+
+ /**
+ * New UTS authentication method
+ * @param umlsUrl
+ * @param umlsApiKey
+ * @return
+ */
+ private boolean isValidUMLSUser(String umlsUrl, String umlsApiKey) {
+ if (_validUsers.contains(umlsApiKey)) {
+ return true;
+ }
+ return doAuth(umlsUrl, umlsApiKey);
+ }
+
+ /**
+ * validate the UMLS license / user
+ * Functionality overridden to deal with new UMLS API
+ *
+ * @param umlsUrl
+ * -
+ * @param vendor IGNORED
+ * -
+ * @param user NOW needs to be the value "umls_api_key"
+ * -
+ * @param pass THE API KEY
+ * -
+ * @return true if the server at umlsaddr approves of the vendor, user,
+ * password combination
+ */
+ public boolean isValidUMLSUser(String umlsUrl, final String vendor,
+ final String user, final String apikey) {
+
+ if (user == null || user.trim().isEmpty() ||
+ !user.equals(this.API_KEY_LABEL)) {
+ LOGGER.error("The user property must now be set to
\'umls_api_key\' ");
+ logCheckUser();
+ return false;
+ }
+
+ if (apikey == null || apikey.trim().isEmpty()) {
+ LOGGER.error("No Api Key supplied");
+ logCheckPass();
+ return false;
+ }
+
+ if (apikey.length() < 24) {
+ LOGGER.error("The password property should be a valid
UTS apikey string");
+ return false;
+ }
+
+ if (_validUsers.contains(apikey)) {
+ return true;
+ }
+
+ // Potentially someone could have a user ID of CHANGEME or a
password of
+ // CHANGEME but don't allow those
+ // to make it easy for us to detect that the user or password
was not
+ // set correctly.
+
+ if (user.equals(CHANGEME) || user.equals(CHANGE_ME)) {
+ LOGGER.error(" User " + user
+ + " not allowed. It is a placeholder
reminder.");
+ logCheckUser();
+ return false;
+ }
+
+ if (apikey.equals(CHANGEME) || apikey.equals(CHANGE_ME)) {
+ LOGGER.error(" Password " + apikey
+ + " not allowed. It is a placeholder
reminder.");
+ logCheckPass();
+ return false;
+ }
+
+ // last chance for an override
+ if (umlsUrl == null || umlsUrl.trim().isEmpty()) {
+ umlsUrl = getUrl();
+ if (umlsUrl == null)
+ umlsUrl = UTS_APIKEY_URL;
+ }
+
+ return doAuth(umlsUrl, apikey);
+ }
+
+ private boolean doAuth(final String umlsUrl, String apiKey) {
+ try (DotLogger dotter = new DotLogger()) {
+ apiKey = apiKey.trim();
+ LOGGER.info("Checking UMLS Account at " + umlsUrl +
":");
+ String data = "apikey="+apiKey;
+ final URL url = new URL(umlsUrl);
+ final HttpURLConnection connection =
(HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(true);
+ final OutputStreamWriter writer = new
OutputStreamWriter(
+ connection.getOutputStream());
+ writer.write(data);
+ writer.flush();
+ boolean isValidUser = false;
+ final BufferedReader reader = new BufferedReader(
+ new
InputStreamReader(connection.getInputStream()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ final String trimline = line.trim();
+ if (trimline.isEmpty()) {
+ break;
+ }
+ }
+ // not used, but in case of problems we may want to see
what is returned
+ LOGGER.debug("UTS response: " + line);
+ writer.close();
+ reader.close();
+ // This method gets a ticket getting token. If it's
successful, thats all we need to know
+ isValidUser = (connection.getResponseCode() ==
HttpURLConnection.HTTP_CREATED);
+ if (isValidUser) {
+ LOGGER.info(" UMLS Account has been
validated");
+ _validUsers.add(apiKey);
+ } else {
+ LOGGER.error(" UMLS Account at " + umlsUrl + "
is not valid.");
+ logCheckUser();
+ logCheckPass();
+ }
+ return isValidUser;
+ } catch (IOException ioE) {
+ LOGGER.error(ioE.getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * used for unit testing
+ */
+ public void resetUserCache() {
+ _validUsers.clear();
+ }
+
+ static private String createLogMessage(String cliOption, String
property,
+ UmlsEnvironmentConfiguration envConfig) {
+ return String
+ .format(" Verify that you are setting
command-line option %s, or ctakes property %s, or environment variable %s
properly.",
+ cliOption, property, envConfig);
+ }
+
+ static private void logCheckUser() {
+ LOGGER.error(createLogMessage(USER_CLI, USER_PARAM,
+ UmlsEnvironmentConfiguration.USER));
+ }
+
+ static private void logCheckPass() {
+ LOGGER.error(createLogMessage(PASS_CLI, PASS_PARAM,
+ UmlsEnvironmentConfiguration.PASSWORD));
+ }
}