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