e02ad4967accecfde73d182321105ed0402167b7
[music.git] / src / main / java / org / onap / music / authentication / CachingUtil.java
1 /*
2  * ============LICENSE_START==========================================
3  * org.onap.music
4  * ===================================================================
5  *  Copyright (c) 2017 AT&T Intellectual Property
6  * ===================================================================
7  *  Modifications Copyright (c) 2018 IBM
8  *  Modifications Copyright (c) 2019 Samsung
9  * ===================================================================
10  *  Licensed under the Apache License, Version 2.0 (the "License");
11  *  you may not use this file except in compliance with the License.
12  *  You may obtain a copy of the License at
13  * 
14  *     http://www.apache.org/licenses/LICENSE-2.0
15  * 
16  *  Unless required by applicable law or agreed to in writing, software
17  *  distributed under the License is distributed on an "AS IS" BASIS,
18  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  *  See the License for the specific language governing permissions and
20  *  limitations under the License.
21  * 
22  * ============LICENSE_END=============================================
23  * ====================================================================
24  */
25
26 package org.onap.music.authentication;
27
28 import java.util.Calendar;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.Map;
32 import javax.ws.rs.core.MediaType;
33
34 import org.apache.commons.codec.binary.Base64;
35 import org.apache.commons.jcs.JCS;
36 import org.apache.commons.jcs.access.CacheAccess;
37 import org.mindrot.jbcrypt.BCrypt;
38 import org.onap.music.authentication.MusicAuthenticator.Operation;
39 import org.onap.music.datastore.PreparedQueryObject;
40 import org.onap.music.eelf.logging.EELFLoggerDelegate;
41 import org.onap.music.eelf.logging.format.AppMessages;
42 import org.onap.music.eelf.logging.format.ErrorSeverity;
43 import org.onap.music.eelf.logging.format.ErrorTypes;
44 import org.onap.music.exceptions.MusicServiceException;
45 import org.onap.music.main.MusicCore;
46 import org.onap.music.main.MusicUtil;
47 import com.datastax.driver.core.DataType;
48 import com.datastax.driver.core.ResultSet;
49 import com.datastax.driver.core.Row;
50 import com.datastax.driver.core.exceptions.InvalidQueryException;
51 import com.sun.jersey.api.client.Client;
52 import com.sun.jersey.api.client.ClientResponse;
53 import com.sun.jersey.api.client.WebResource;
54
55 /**
56  * All Caching related logic is handled by this class and a schedule cron runs to update cache.
57  *
58  * @author Vikram
59  *
60  */
61 public class CachingUtil implements Runnable {
62
63     private static EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(CachingUtil.class);
64
65     /** keyspace & ns */
66     private static CacheAccess<String, String> musicCache = JCS.getInstance("musicCache");
67     /** cache to hold isaaf application */
68     private static CacheAccess<String, String> appNameCache = JCS.getInstance("appNameCache");
69     /** hold user creds for namespace */
70     private static CacheAccess<String, Map<String, String>> musicValidateCache = JCS.getInstance("musicValidateCache");
71     private static Map<String, Number> userAttempts = new HashMap<>();
72     private static Map<String, Calendar> lastFailedTime = new HashMap<>();
73     private static CacheAccess<String, String> adminUserCache = JCS.getInstance("adminUserCache");
74
75     public static CacheAccess<String, String> getAdminUserCache() {
76         return adminUserCache;
77     }
78
79     public static void updateAdminUserCache(String authorization,String userId) {
80         adminUserCache.put(authorization,userId);
81     }
82
83     private static final String USERNAME="username";
84     private static final String PASSWORD="password";
85
86
87     public void initializeAafCache() throws MusicServiceException {
88         logger.info(EELFLoggerDelegate.applicationLogger,"Resetting and initializing AAF Cache...");
89
90         String query = "SELECT uuid, application_name, keyspace_name, username, password FROM admin.keyspace_master WHERE is_api = ? allow filtering";
91         PreparedQueryObject pQuery = new PreparedQueryObject();
92         pQuery.appendQueryString(query);
93         try {
94             pQuery.addValue(MusicUtil.convertToActualDataType(DataType.cboolean(), false));
95         } catch (Exception e1) {
96             logger.error(EELFLoggerDelegate.errorLogger, e1.getMessage(),AppMessages.CACHEERROR, ErrorSeverity
97                 .CRITICAL, ErrorTypes.GENERALSERVICEERROR, e1);
98         }
99         ResultSet rs = MusicCore.get(pQuery);
100         Iterator<Row> it = rs.iterator();
101         Map<String, String> map = null;
102         while (it.hasNext()) {
103             Row row = it.next();
104             String nameSpace = row.getString("keyspace_name");
105             String userId = row.getString(USERNAME);
106             String password = row.getString(PASSWORD);
107             String keySpace = row.getString("application_name");
108             try {
109                 userAttempts.put(nameSpace, 0);
110                 boolean responseObj = triggerAAF(nameSpace, userId, password);
111                 if (responseObj) {
112                     map = new HashMap<>();
113                     map.put(userId, password);
114                     musicValidateCache.put(nameSpace, map);
115                     musicCache.put(keySpace, nameSpace);
116                     logger.debug("Cronjob: Cache Updated with AAF response for namespace "
117                                     + nameSpace);
118                 }
119             } catch (Exception e) {
120                 logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.UNKNOWNERROR, ErrorSeverity
121                     .INFO, ErrorTypes.GENERALSERVICEERROR, e);
122                 logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),"Something at AAF was changed for ns: " + nameSpace+" So not updating Cache for the namespace. ");
123             }
124         }
125
126     }
127
128     @Override
129     public void run() {
130         logger.info(EELFLoggerDelegate.applicationLogger,"Scheduled task invoked. Refreshing Cache...");
131         try {
132             initializeAafCache();
133         } catch (MusicServiceException e) {
134             logger.error(EELFLoggerDelegate.errorLogger, e.getMessage(),AppMessages.UNKNOWNERROR, ErrorSeverity.INFO,
135                 ErrorTypes.GENERALSERVICEERROR, e);
136         }
137     }
138
139     public static boolean authenticateAAFUser(String nameSpace, String userId, String password,
140                     String keySpace) throws Exception {
141
142         if (musicValidateCache.get(nameSpace) != null && musicCache.get(keySpace)!=null) {
143             if (keySpace != null && !musicCache.get(keySpace).equals(nameSpace)) {
144                 logger.info(EELFLoggerDelegate.applicationLogger,"Create new application for the same namespace.");
145             } else if (musicValidateCache.get(nameSpace).get(userId).equals(password)) {
146                 logger.info(EELFLoggerDelegate.applicationLogger,"Authenticated with cache value..");
147                 // reset invalid attempts to 0
148                 userAttempts.put(nameSpace, 0);
149                 return true;
150             } else {
151                 // call AAF update cache with new password
152                 if (userAttempts.get(nameSpace) == null)
153                     userAttempts.put(nameSpace, 0);
154                 if ((Integer) userAttempts.get(nameSpace) >= 3) {
155                     logger.info(EELFLoggerDelegate.applicationLogger,"Reached max attempts. Checking if time out..");
156                     logger.info(EELFLoggerDelegate.applicationLogger,"Failed time: "+lastFailedTime.get(nameSpace).getTime());
157                     Calendar calendar = Calendar.getInstance();
158                     long delayTime = (calendar.getTimeInMillis()-lastFailedTime.get(nameSpace).getTimeInMillis());
159                     logger.info(EELFLoggerDelegate.applicationLogger,"Delayed time: "+delayTime);
160                     if( delayTime > 120000) {
161                         logger.info(EELFLoggerDelegate.applicationLogger,"Resetting failed attempt.");
162                         userAttempts.put(nameSpace, 0);
163                     } else {
164                         logger.info(EELFLoggerDelegate.applicationLogger,"No more attempts allowed. Please wait for atleast 2 min.");
165                         throw new Exception("No more attempts allowed. Please wait for atleast 2 min.");
166                     }
167                 }
168                 logger.error(EELFLoggerDelegate.errorLogger,"",AppMessages.CACHEAUTHENTICATION,ErrorSeverity.WARN, ErrorTypes.GENERALSERVICEERROR);
169                 logger.info(EELFLoggerDelegate.applicationLogger,"Check AAF again...");
170             }
171         }
172
173         boolean responseObj = false;
174         try {
175             responseObj = triggerAAF(nameSpace, userId, password);
176         }catch (Exception ex) {
177             logger.info("Exception while trigger aaf", ex);
178             logger.info("Exception: " + ex.getMessage());
179             throw new Exception("Exception raised while triggering AAF authentication" +ex.getMessage());
180         }
181         if (responseObj) {
182             logger.info(EELFLoggerDelegate.applicationLogger,"Valid user. Cache is updated for "+nameSpace);
183                 Map<String, String> map = new HashMap<>();
184                 map.put(userId, password);
185                 musicValidateCache.put(nameSpace, map);
186                 musicCache.put(keySpace, nameSpace);
187                 return true;
188         }
189         logger.info(EELFLoggerDelegate.applicationLogger,"Invalid user. Cache not updated");
190         return false;
191     }
192
193     private static boolean triggerAAF(String nameSpace, String userId, String password)
194                     throws Exception {
195         logger.info(EELFLoggerDelegate.applicationLogger,"Inside AAF authorization");
196         if (MusicUtil.getAafEndpointUrl() == null) {
197             logger.error(EELFLoggerDelegate.errorLogger,"AAF endpoint is not set. Please specify in the properties file.",AppMessages.UNKNOWNERROR,ErrorSeverity.WARN, ErrorTypes.GENERALSERVICEERROR);
198             throw new Exception("AAF endpoint is not set. Please specify in the properties file.");
199         }
200         Client client = Client.create();
201         WebResource webResource = client.resource(MusicUtil.getAafEndpointUrl().concat(nameSpace));
202         String plainCreds = userId + ":" + password;
203         byte[] plainCredsBytes = plainCreds.getBytes();
204         byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes);
205         String base64Creds = new String(base64CredsBytes);
206
207         ClientResponse response = webResource.accept(MediaType.APPLICATION_JSON)
208                         .header("Authorization", "Basic " + base64Creds)
209                         .header("content-type", "application/json").get(ClientResponse.class);
210         logger.info(EELFLoggerDelegate.applicationLogger, "aaf response: "+response.toString());
211         if (response.getStatus() != 200) {
212             if (userAttempts.get(nameSpace) == null)
213                 userAttempts.put(nameSpace, 0);
214             if ((Integer) userAttempts.get(nameSpace) >= 2) {
215                 lastFailedTime.put(nameSpace, Calendar.getInstance());
216                 userAttempts.put(nameSpace, ((Integer) userAttempts.get(nameSpace) + 1));
217                 throw new Exception(
218                                 "Reached max invalid attempts. Please contact admin and retry with valid credentials.");
219             }
220             userAttempts.put(nameSpace, ((Integer) userAttempts.get(nameSpace) + 1));
221             throw new Exception(
222                             "Unable to authenticate. Please check the AAF credentials against namespace.");
223             // TODO Allow for 2-3 times and forbid any attempt to trigger AAF with invalid values
224             // for specific time.
225         }
226         /*response.getHeaders().put(HttpHeaders.CONTENT_TYPE,
227                         Arrays.asList(MediaType.APPLICATION_JSON));
228         // AAFResponse output = response.getEntity(AAFResponse.class);
229         response.bufferEntity();
230         String x = response.getEntity(String.class);
231         AAFResponse responseObj = new ObjectMapper().readValue(x, AAFResponse.class);*/
232
233         return true;
234     }
235
236     public static void updateMusicCache(String keyspace, String nameSpace) {
237         logger.info(EELFLoggerDelegate.applicationLogger,"Updating musicCache for keyspace " + keyspace + " with nameSpace " + nameSpace);
238         musicCache.put(keyspace, nameSpace);
239     }
240
241     public static void updateCadiCache(String user, String keyspace) {
242         musicCache.put(user, keyspace);
243     }
244
245     public static String getKSFromCadiCache(String user) {
246         return musicCache.get(user);
247     }
248
249     public static void updateMusicValidateCache(String nameSpace, String userId, String password) {
250         logger.info(EELFLoggerDelegate.applicationLogger,"Updating musicCache for nameSpacce " + nameSpace + " with userId " + userId);
251         Map<String, String> map = new HashMap<>();
252         map.put(userId, password);
253         musicValidateCache.put(nameSpace, map);
254     }
255
256     public static void updateisAAFCache(String namespace, String isAAF) {
257         appNameCache.put(namespace, isAAF);
258     }
259
260     public static String isAAFApplication(String namespace) throws MusicServiceException {
261         String isAAF = appNameCache.get(namespace);
262         if (isAAF == null) {
263             PreparedQueryObject pQuery = new PreparedQueryObject();
264             pQuery.appendQueryString(
265                             "SELECT is_aaf from admin.keyspace_master where application_name = '"
266                                             + namespace + "' allow filtering");
267             Row rs = null;
268             try {
269                 rs = MusicCore.get(pQuery).one();
270             } catch(InvalidQueryException e) {
271                 logger.error(EELFLoggerDelegate.errorLogger,"Exception admin keyspace not configured."+ e.getMessage
272                     (), e);
273                 throw new MusicServiceException("Please make sure admin.keyspace_master table is configured.");
274             }
275             try {
276                 isAAF = String.valueOf(rs.getBool("is_aaf"));
277                 if(isAAF != null)
278                     appNameCache.put(namespace, isAAF);
279             } catch (Exception e) {
280                 logger.error(EELFLoggerDelegate.errorLogger,  e.getMessage(), AppMessages.QUERYERROR,ErrorSeverity
281                     .ERROR, ErrorTypes.QUERYERROR, e);
282             }
283         }
284         return isAAF;
285     }
286
287     public static String getUuidFromMusicCache(String keyspace) throws MusicServiceException {
288         String uuid = null;
289         if (uuid == null) {
290             PreparedQueryObject pQuery = new PreparedQueryObject();
291             pQuery.appendQueryString(
292                             "SELECT uuid from admin.keyspace_master where keyspace_name = '"
293                                             + keyspace + "' allow filtering");
294             Row rs = MusicCore.get(pQuery).one();
295             try {
296                 uuid = rs.getUUID("uuid").toString();
297             } catch (Exception e) {
298                 logger.error(EELFLoggerDelegate.errorLogger,"Exception occurred during uuid retrieval from DB."+e
299                     .getMessage(), e);
300             }
301         }
302         return uuid;
303     }
304
305     public static String getAppName(String keyspace) throws MusicServiceException {
306         String appName = null;
307         PreparedQueryObject pQuery = new PreparedQueryObject();
308         pQuery.appendQueryString(
309                         "SELECT application_name from admin.keyspace_master where keyspace_name = '"
310                                         + keyspace + "' allow filtering");
311         Row rs = MusicCore.get(pQuery).one();
312         try {
313             appName = rs.getString("application_name");
314         } catch (Exception e) {
315             logger.error(EELFLoggerDelegate.errorLogger,  e.getMessage(), AppMessages.QUERYERROR, ErrorSeverity
316                 .ERROR, ErrorTypes.QUERYERROR, e);
317         }
318         return appName;
319     }
320
321     @Deprecated
322     public static Map<String, Object> validateRequest(String nameSpace, String userId,
323                     String password, String keyspace, String aid, String operation) {
324         Map<String, Object> resultMap = new HashMap<>();
325         if (!"createKeySpace".equals(operation)) {
326             if (nameSpace == null) {
327                 resultMap.put("Exception", "Application namespace is mandatory.");
328             }
329         }
330         return resultMap;
331     }
332
333     public static Map<String, Object> validateRequest(String nameSpace, String userId,
334             String password, String keyspace, String aid, Operation operation) {
335         Map<String, Object> resultMap = new HashMap<>();
336         if (Operation.CREATE_KEYSPACE!=operation) {
337             if (nameSpace == null) {
338                 resultMap.put("Exception", "Application namespace is mandatory.");
339             }
340         }
341         return resultMap;
342     }
343
344     public static Map<String, Object> verifyOnboarding(String ns, String userId, String password) {
345         Map<String, Object> resultMap = new HashMap<>();
346         if (ns == null || userId == null || password == null) {
347             logger.error(EELFLoggerDelegate.errorLogger,"", AppMessages.MISSINGINFO ,ErrorSeverity.WARN, ErrorTypes.AUTHENTICATIONERROR);
348             logger.error(EELFLoggerDelegate.errorLogger,"One or more required headers is missing. userId: "+userId+" :: password: "+password);
349             resultMap.put("Exception",
350                             "One or more required headers appName(ns), userId, password is missing. Please check.");
351             return resultMap;
352         }
353         PreparedQueryObject queryObject = new PreparedQueryObject();
354         queryObject.appendQueryString(
355                         "select * from admin.keyspace_master where application_name = ? allow filtering");
356         try {
357             queryObject.addValue(MusicUtil.convertToActualDataType(DataType.text(), ns));
358         } catch(Exception e) {
359             resultMap.put("Exception",
360                     "Unable to process input data. Invalid input data type. Please check ns, userId and password values. "+e.getMessage());
361             return resultMap;
362         }
363         Row rs = null;
364         try {
365             rs = MusicCore.get(queryObject).one();
366         } catch (MusicServiceException e) {
367             String errorMsg = "Unable to process operation. Error is "+e.getMessage();
368             logger.error(EELFLoggerDelegate.errorLogger, errorMsg, e);
369             resultMap.put("Exception", errorMsg);
370             return resultMap;
371         } catch (InvalidQueryException e) {
372             logger.error(EELFLoggerDelegate.errorLogger,"Exception admin keyspace not configured."+e.getMessage(), e);
373             resultMap.put("Exception", "Please make sure admin.keyspace_master table is configured.");
374             return resultMap;
375         }
376         if (rs == null) {
377             logger.error(EELFLoggerDelegate.errorLogger,"Application is not onboarded. Please contact admin.");
378             resultMap.put("Exception", "Application is not onboarded. Please contact admin.");
379         } else {
380             if(!(rs.getString(USERNAME).equals(userId)) || !(BCrypt.checkpw(password, rs.getString(PASSWORD)))) {
381                 logger.error(EELFLoggerDelegate.errorLogger,"", AppMessages.AUTHENTICATIONERROR, ErrorSeverity.WARN, ErrorTypes.AUTHENTICATIONERROR);
382                 logger.error(EELFLoggerDelegate.errorLogger,"Namespace, UserId and password doesn't match. namespace: "+ns+" and userId: "+userId);
383                 resultMap.put("Exception", "Namespace, UserId and password doesn't match. namespace: "+ns+" and userId: "+userId);
384                 return resultMap;
385             }
386         }
387         return resultMap;
388     }
389
390     public static Map<String, Object> authenticateAIDUser(String nameSpace, String userId, String password,
391            String keyspace) {
392         Map<String, Object> resultMap = new HashMap<>();
393         String pwd = null;
394         if((musicCache.get(keyspace) != null) && (musicValidateCache.get(nameSpace) != null)
395                 && (musicValidateCache.get(nameSpace).containsKey(userId))) {
396             if(!musicCache.get(keyspace).equals(nameSpace)) {
397                 resultMap.put("Exception", "Namespace and keyspace doesn't match");
398                 return resultMap;
399             }
400             if(!BCrypt.checkpw(password,musicValidateCache.get(nameSpace).get(userId))) {
401                 resultMap.put("Exception", "Namespace, userId and password doesn't match");
402                 return resultMap;
403             }
404             return resultMap;
405         }
406         PreparedQueryObject queryObject = new PreparedQueryObject();
407         queryObject.appendQueryString(
408                         "select * from admin.keyspace_master where keyspace_name = ? allow filtering");
409         try {
410             queryObject.addValue(MusicUtil.convertToActualDataType(DataType.text(), keyspace));
411         } catch (Exception e) {
412             logger.error(EELFLoggerDelegate.errorLogger,"Adding value to query object failed: " + e.getMessage(), e);
413         }
414         Row rs = null;
415         try {
416             rs = MusicCore.get(queryObject).one();
417         } catch (MusicServiceException e) {
418             String errMsg = "Unable to process operation. Error is "+e.getMessage();
419             logger.error(EELFLoggerDelegate.errorLogger, errMsg, e);
420             resultMap.put("Exception", errMsg);
421             return resultMap;
422         }
423         if(rs == null) {
424             resultMap.put("Exception", "Please make sure keyspace:"+keyspace+" exists.");
425             return resultMap;
426         }
427         else {
428             String user = rs.getString(USERNAME);
429             pwd = rs.getString(PASSWORD);
430             String ns = rs.getString("application_name");
431             if(!ns.equals(nameSpace)) {
432             resultMap.put("Exception", "Namespace and keyspace doesn't match");
433             return resultMap;
434             }
435             if(!user.equals(userId)) {
436                 resultMap.put("Exception", "Invalid userId :"+userId);
437                 return resultMap;
438             }
439             if(!BCrypt.checkpw(password, pwd)) {
440                 resultMap.put("Exception", "Invalid password");
441                 return resultMap;
442             }
443         }
444         CachingUtil.updateMusicCache(keyspace, nameSpace);
445         CachingUtil.updateMusicValidateCache(nameSpace, userId, pwd);
446         return resultMap;
447     }
448 }