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));
+       }
 }


Reply via email to