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