001 package org.hackystat.dailyprojectdata.server; 002 003 import java.util.HashMap; 004 005 import java.util.Map; 006 007 import org.hackystat.sensorbase.client.SensorBaseClient; 008 import org.restlet.Context; 009 import org.restlet.Guard; 010 import org.restlet.data.ChallengeScheme; 011 import org.restlet.data.Request; 012 013 /** 014 * Performs authentication of each HTTP request using HTTP Basic authentication. 015 * Checks user/password credentials by pinging SensorBase, then caching authentic 016 * user/password combinations. If a cached user/password combo does not match the 017 * current user/password combo, then the SensorBase is pinged again (because maybe 018 * the user has changed their password recently). 019 * <p> 020 * Because DPD resources will always want to communicate with the underlying SensorBase, this 021 * Authenticator also creates a map of user-to-SensorBaseClient instances which can be retrieved 022 * from the server context. This keeps the user password info in this class, while making the 023 * SensorBaseClient instance available to the service. 024 * @author Philip Johnson 025 */ 026 public class Authenticator extends Guard { 027 028 /** A singleton map containing previously verified credentials. */ 029 private static Map<String, String> credentials = new HashMap<String, String>(); 030 031 /** A singleton map containing SensorBaseClient instances, one per credentialed user. */ 032 private static Map<String, SensorBaseClient> userClientMap = 033 new HashMap<String, SensorBaseClient>(); 034 035 /** The sensorbase host, such as "http://localhost:9876/sensorbase/". */ 036 private String sensorBaseHost; 037 038 /** The key to be used to retrieve the sensorbaseclient map from the server context. */ 039 public static final String AUTHENTICATOR_SENSORBASECLIENTS_KEY = "authenticator.clients"; 040 041 /** 042 * Initializes this Guard to do HTTP Basic authentication. 043 * Puts the credentials map in the server context so that Resources can get the 044 * password associated with the uriUser for their own invocations to the SensorBase. 045 * @param context The server context. 046 * @param sensorBaseHost The host, such as 'http://localhost:9876/sensorbase/'. 047 */ 048 public Authenticator (Context context, String sensorBaseHost) { 049 super(context, ChallengeScheme.HTTP_BASIC, "DailyProjectData"); 050 this.sensorBaseHost = sensorBaseHost; 051 context.getAttributes().put(AUTHENTICATOR_SENSORBASECLIENTS_KEY, userClientMap); 052 } 053 054 /** 055 * Returns true if the passed credentials are OK. 056 * @param request Ignored. 057 * @param identifier The account name. 058 * @param secretCharArray The password. 059 * @return If the credentials are valid. 060 */ 061 @Override public boolean checkSecret(Request request, 062 String identifier, char[] secretCharArray) { 063 /* 064 * I am synchronizing here on a static (class-wide) variable for two reasons: 065 * (1) JCS write-through caching fails when multiple threads access the same back-end file: 066 * <https://issues.apache.org/jira/browse/JCS-31>. Thus, it is vitally important to ensure 067 * that only one instance of a SensorBaseClient for any given user is created. 068 * (2) I do not know if Restlet allows multiple Authenticator instances. Thus, I am 069 * synchronizing on a class-wide variable just in case. 070 * This synchronization creates a bottleneck on every request, but the benefits of reliable 071 * caching should outweigh this potential performance hit under high loads. 072 */ 073 synchronized (userClientMap) { 074 String secret = new String(secretCharArray); 075 // Return true if the user/password credentials are in the cache. 076 if (credentials.containsKey(identifier) && secret.equals(credentials.get(identifier))) { 077 return true; 078 } 079 // Otherwise we check the credentials with the SensorBase. 080 boolean isRegistered = SensorBaseClient.isRegistered(sensorBaseHost, identifier, secret); 081 if (isRegistered) { 082 // Credentials are good, so save them and create a sensorbase client for this user. 083 credentials.put(identifier, secret); 084 SensorBaseClient client = new SensorBaseClient(sensorBaseHost, identifier, secret); 085 // Set timeout to 60 minutes. 086 client.setTimeout(1000 * 60 * 60); 087 // Get the ServerProperties instance so we can determine if caching is enabled. 088 Server server = (Server) getContext().getAttributes().get("DailyProjectDataServer"); 089 ServerProperties props = server.getServerProperties(); 090 if (props.isCacheEnabled()) { 091 client.enableCaching(identifier, "dailyprojectdata", props.getCacheMaxLife(), props 092 .getCacheCapacity()); 093 } 094 userClientMap.put(identifier, client); 095 } 096 return isRegistered; 097 } 098 } 099 }