Refactor logging to use slf4j
[iotivity.git] / cloud / account / src / main / java / org / iotivity / cloud / accountserver / resources / account / AccountManager.java
1 /*
2  * //******************************************************************
3  * //
4  * // Copyright 2016 Samsung Electronics All Rights Reserved.
5  * //
6  * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
7  * //
8  * // Licensed under the Apache License, Version 2.0 (the "License");
9  * // you may not use this file except in compliance with the License.
10  * // You may obtain a copy of the License at
11  * //
12  * //      http://www.apache.org/licenses/LICENSE-2.0
13  * //
14  * // Unless required by applicable law or agreed to in writing, software
15  * // distributed under the License is distributed on an "AS IS" BASIS,
16  * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * // See the License for the specific language governing permissions and
18  * // limitations under the License.
19  * //
20  * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
21  */
22 package org.iotivity.cloud.accountserver.resources.account;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.text.DateFormat;
30 import java.text.ParseException;
31 import java.text.SimpleDateFormat;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Date;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.UUID;
40
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43 import org.iotivity.cloud.accountserver.Constants;
44 import org.iotivity.cloud.accountserver.db.AccountDBManager;
45 import org.iotivity.cloud.accountserver.db.TokenTable;
46 import org.iotivity.cloud.accountserver.db.UserTable;
47 import org.iotivity.cloud.accountserver.oauth.OAuthProviderFactory;
48 import org.iotivity.cloud.accountserver.resources.acl.group.GroupBrokerManager;
49 import org.iotivity.cloud.accountserver.resources.acl.group.GroupManager;
50 import org.iotivity.cloud.accountserver.resources.acl.id.AclResource;
51 import org.iotivity.cloud.accountserver.util.TypeCastingManager;
52 import org.iotivity.cloud.base.exception.ServerException.BadRequestException;
53 import org.iotivity.cloud.base.exception.ServerException.InternalServerErrorException;
54 import org.iotivity.cloud.base.exception.ServerException.NotFoundException;
55 import org.iotivity.cloud.base.exception.ServerException.UnAuthorizedException;
56
57 /**
58  *
59  * This class provides a set of APIs to handle requests about account
60  * information of authorized user.
61  *
62  */
63 public class AccountManager {
64     private final static Logger            Log                       = LoggerFactory.getLogger(AccountManager.class);
65     private OAuthProviderFactory           mFactory                  = null;
66     private TypeCastingManager<UserTable>  mUserTableCastingManager  = new TypeCastingManager<>();
67     private TypeCastingManager<TokenTable> mTokenTableCastingManager = new TypeCastingManager<>();
68
69     /**
70      * API to return a sign-up response payload
71      * 
72      * @param did
73      *            Device id registered under user account
74      * @param authCode
75      *            Unique identifier of the resource which is obtained from an
76      *            auth provider or a single sign-on (SSO) client
77      * @param authProviderName
78      *            Provider name user for authentication (e.g., "Github")
79      * @param options
80      *            Optional field (e.g., region authserver url, apiserver url)
81      * 
82      * @return Sign-up response payload
83      */
84
85     public HashMap<String, Object> signUp(String did, String authCode,
86             String authProviderName, Object options) {
87
88         authProviderName = checkAuthProviderName(authProviderName);
89         boolean res = loadAuthProviderLibrary(authProviderName);
90
91         if (!res) {
92             throw new InternalServerErrorException(
93                     authProviderName + " library is not loaded");
94         }
95         String userUuid = null;
96         // set token data
97         TokenTable tokenInfo = requestAccessToken(authCode, options);
98         tokenInfo.setDid(did);
99         tokenInfo.setProvider(authProviderName);
100         Date currentTime = new Date();
101         DateFormat transFormat = new SimpleDateFormat("yyyyMMddkkmm");
102         tokenInfo.setIssuedtime(transFormat.format(currentTime));
103
104         // set user data
105         UserTable userInfo = requestUserInfo(tokenInfo.getAccesstoken(),
106                 options);
107         userInfo.setProvider(authProviderName);
108
109         // check uuid
110         userUuid = findUuid(userInfo.getUserid(), authProviderName);
111
112         // store token information and user information to the DB
113         // private group creation and store group information to the DB
114         userUuid = storeUserTokenInfo(userUuid, userInfo, tokenInfo, did);
115
116         AclResource.getInstance().createAcl(userUuid, did);
117
118         // make response
119         HashMap<String, Object> response = makeSignUpResponse(tokenInfo);
120
121         return response;
122     }
123
124     /**
125      * API to return a sign-in or sign-out response payload
126      * 
127      * @param uuid
128      *            User id which is provided by Sign-up process
129      * @param did
130      *            Device id registered under user account
131      * @param accessToken
132      *            Access token used for communication with cloud
133      * @return Sign-in or sign-out response payload
134      */
135     public HashMap<String, Object> signInOut(String uuid, String did,
136             String accessToken) {
137
138         // find token information corresponding to the uuid and did
139         HashMap<String, Object> condition = new HashMap<>();
140         condition.put(Constants.KEYFIELD_UUID, uuid);
141         condition.put(Constants.KEYFIELD_DID, did);
142
143         ArrayList<HashMap<String, Object>> recordList = AccountDBManager
144                 .getInstance().selectRecord(Constants.TOKEN_TABLE, condition);
145
146         if (recordList.isEmpty()) {
147             throw new UnAuthorizedException("access token doesn't exist");
148         }
149
150         TokenTable tokenInfo = castMapToTokenTable(recordList.get(0));
151
152         // token verification to check if the accesstoken is expired
153         if (verifyToken(tokenInfo, accessToken)) {
154             long remainedSeconds = getRemainedSeconds(
155                     tokenInfo.getExpiredtime(), tokenInfo.getIssuedtime());
156
157             return makeSignInResponse(remainedSeconds);
158         } else {
159             throw new UnAuthorizedException("AccessToken is unauthorized");
160         }
161     }
162
163     /**
164      * API to return a token refresh response payload
165      * 
166      * @param uuid
167      *            user id which is provided by Sign-up process
168      * @param did
169      *            device id registered under user account
170      * @param grantType
171      *            token type to be granted
172      * @param refreshToken
173      *            Refresh token used to refresh the access token in cloud before
174      *            getting expired
175      * @return Token refresh response payload
176      */
177
178     public HashMap<String, Object> refreshToken(String uuid, String did,
179             String grantType, String refreshToken) {
180
181         // find record about uuid and did
182         HashMap<String, Object> condition = new HashMap<>();
183         condition.put(Constants.KEYFIELD_UUID, uuid);
184         condition.put(Constants.KEYFIELD_DID, did);
185
186         ArrayList<HashMap<String, Object>> recordList = findRecord(
187                 AccountDBManager.getInstance()
188                         .selectRecord(Constants.TOKEN_TABLE, condition),
189                 Constants.KEYFIELD_DID, did);
190
191         if (recordList.isEmpty()) {
192             throw new NotFoundException("refresh token doesn't exist");
193         }
194
195         HashMap<String, Object> record = recordList.get(0);
196
197         TokenTable oldTokenInfo = castMapToTokenTable(record);
198         String provider = oldTokenInfo.getProvider();
199
200         if (!checkRefreshTokenInDB(oldTokenInfo, refreshToken)) {
201             throw new NotFoundException("refresh token is not correct");
202         }
203         // call 3rd party refresh token method
204         TokenTable newTokenInfo = requestRefreshToken(refreshToken, provider);
205
206         // record change
207         oldTokenInfo.setAccesstoken(newTokenInfo.getAccesstoken());
208         oldTokenInfo.setRefreshtoken(newTokenInfo.getRefreshtoken());
209
210         // insert record
211         AccountDBManager.getInstance().insertAndReplaceRecord(
212                 Constants.TOKEN_TABLE, castTokenTableToMap(oldTokenInfo));
213
214         // make response
215         HashMap<String, Object> response = makeRefreshTokenResponse(
216                 oldTokenInfo);
217
218         return response;
219     }
220
221     private String storeUserTokenInfo(String userUuid, UserTable userInfo,
222             TokenTable tokenInfo, String did) {
223         // store db
224         // the user table is created
225         if (userUuid == null) {
226             userUuid = generateUuid();
227             userInfo.setUuid(userUuid);
228
229             AccountDBManager.getInstance().insertRecord(Constants.USER_TABLE,
230                     castUserTableToMap(userInfo));
231
232             // make my private group
233             GroupBrokerManager.getInstance().createGroup(userInfo.getUuid(),
234                     userInfo.getUuid(), null, null);
235         }
236         tokenInfo.setUuid(userUuid);
237         AccountDBManager.getInstance().insertAndReplaceRecord(
238                 Constants.TOKEN_TABLE, castTokenTableToMap(tokenInfo));
239         return userUuid;
240     }
241
242     private String checkAuthProviderName(String authProviderName) {
243         String libraryFileName = getValidFileName(Constants.OAUTH_LIBRARIES_PATH, authProviderName + ".jar");
244         if (libraryFileName == null) {
245             Log.warn("OAuth 3rd party library " + authProviderName + " does not exist.");
246             return authProviderName;
247         }
248         return libraryFileName.substring(0, libraryFileName.length() - 4);
249     }
250
251     private String getValidFileName(String path, String filename) {
252         File file = new File(path + filename);
253         if(file.exists())
254             return filename;
255
256         File parentFile = file.getAbsoluteFile().getParentFile();
257         if (parentFile.exists())
258             for (String directoryFile : parentFile.list())
259                 if (directoryFile.equalsIgnoreCase(file.getName()))
260                     return directoryFile;
261
262         return null;
263     }
264
265     private String findUuid(String userId, String authProvider) {
266         String uuid = null;
267
268         HashMap<String, Object> condition = new HashMap<>();
269         condition.put(Constants.KEYFIELD_USERID, userId);
270
271         ArrayList<HashMap<String, Object>> recordList = AccountDBManager
272                 .getInstance().selectRecord(Constants.USER_TABLE, condition);
273
274         for (HashMap<String, Object> record : recordList) {
275             String foundProvider = record.get(Constants.KEYFIELD_PROVIDER)
276                     .toString();
277             if (foundProvider != null
278                     && foundProvider.equalsIgnoreCase(authProvider)) {
279                 return record.get(Constants.KEYFIELD_UUID).toString();
280             }
281         }
282         return uuid;
283     }
284
285     private HashMap<String, Object> castUserTableToMap(UserTable userInfo) {
286
287         return mUserTableCastingManager.convertObjectToMap(userInfo);
288     }
289
290     private HashMap<String, Object> castTokenTableToMap(TokenTable tokenInfo) {
291
292         return mTokenTableCastingManager.convertObjectToMap(tokenInfo);
293     }
294
295     private TokenTable castMapToTokenTable(HashMap<String, Object> record) {
296         TokenTable tokenInfo = new TokenTable();
297         return mTokenTableCastingManager.convertMaptoObject(record, tokenInfo);
298     }
299
300     private HashMap<String, Object> makeSignUpResponse(TokenTable tokenInfo) {
301
302         HashMap<String, Object> response = new HashMap<>();
303
304         response.put(Constants.RESP_ACCESS_TOKEN, tokenInfo.getAccesstoken());
305         response.put(Constants.RESP_REFRESH_TOKEN, tokenInfo.getRefreshtoken());
306         response.put(Constants.RESP_TOKEN_TYPE, Constants.TOKEN_TYPE_BEARER);
307         response.put(Constants.RESP_EXPIRES_IN, tokenInfo.getExpiredtime());
308         response.put(Constants.RESP_UUID, tokenInfo.getUuid());
309
310         // It will be modified.
311         response.put(Constants.RESP_REDIRECT_URI, getRegionCIUrl());
312         response.put(Constants.RESP_CERTIFICATE, getRootCert());
313         response.put(Constants.RESP_SERVER_ID, Constants.CLOUD_UUID);
314
315         return response;
316     }
317
318     private String getRegionCIUrl() {
319
320         // TODO: add region management
321         return "coap+tcp://127.0.0.1:5683";
322     }
323
324     private byte[] getRootCert() {
325
326         byte[] byteRootCert = null;
327
328         Path path = Paths.get(Constants.ROOT_CERT_FILE);
329
330         try {
331
332             byteRootCert = Files.readAllBytes(path);
333
334         } catch (IOException e) {
335
336             e.printStackTrace();
337             // throw new InternalServerErrorException(
338             // "root cert file read failed!");
339         }
340
341         return byteRootCert;
342     }
343
344     private Boolean loadAuthProviderLibrary(String authProviderName) {
345         mFactory = new OAuthProviderFactory();
346
347         return mFactory.load(authProviderName);
348     }
349
350     private TokenTable requestAccessToken(String authCode, Object options) {
351         TokenTable tokenInfo = mFactory.requestAccessTokenInfo(authCode,
352                 options);
353         Log.debug("access token : " + tokenInfo.getAccesstoken());
354         Log.debug("refresh token : " + tokenInfo.getRefreshtoken());
355         Log.debug("expired time : " + tokenInfo.getExpiredtime());
356
357         return tokenInfo;
358     }
359
360     private UserTable requestUserInfo(String accessToken, Object options) {
361         UserTable userInfo = mFactory.requestGetUserInfo(accessToken, options);
362         Log.debug("user id  : " + userInfo.getUserid());
363
364         return userInfo;
365     }
366
367     private String generateUuid() {
368         UUID uuid = UUID.randomUUID();
369         String userUuid = uuid.toString();
370         Log.debug("generated uuid : " + userUuid);
371         return userUuid;
372     }
373
374     private ArrayList<HashMap<String, Object>> findRecord(
375             ArrayList<HashMap<String, Object>> recordList, String fieldName,
376             String value) {
377         ArrayList<HashMap<String, Object>> foundRecord = new ArrayList<>();
378
379         for (HashMap<String, Object> record : recordList) {
380             Object obj = record.get(fieldName);
381             if (obj != null && obj.equals(value)) {
382                 foundRecord.add(record);
383             }
384         }
385         return foundRecord;
386     }
387
388     private HashMap<String, Object> makeSignInResponse(long remainedSeconds) {
389         HashMap<String, Object> response = new HashMap<>();
390         response.put(Constants.RESP_EXPIRES_IN, remainedSeconds);
391
392         return response;
393     }
394
395     private long getRemainedSeconds(long expiredTime, String issuedTime) {
396         if (expiredTime == Constants.TOKEN_INFINITE) {
397             return Constants.TOKEN_INFINITE;
398         } else {
399             return expiredTime - getElaspedSeconds(issuedTime);
400         }
401     }
402
403     private boolean verifyToken(TokenTable tokenInfo, String accessToken) {
404
405         if (!checkAccessTokenInDB(tokenInfo, accessToken)) {
406             return false;
407         }
408
409         if (tokenInfo.getExpiredtime() != Constants.TOKEN_INFINITE
410                 && !checkExpiredTime(tokenInfo)) {
411             return false;
412         }
413
414         return true;
415     }
416
417     private boolean checkRefreshTokenInDB(TokenTable tokenInfo, String token) {
418         if (tokenInfo.getRefreshtoken() == null) {
419             Log.warn("Refreshtoken doesn't exist");
420             return false;
421         } else if (!tokenInfo.getRefreshtoken().equals(token)) {
422             Log.warn("Refreshtoken is not correct");
423             return false;
424         }
425         return true;
426     }
427
428     private boolean checkAccessTokenInDB(TokenTable tokenInfo, String token) {
429         if (tokenInfo.getAccesstoken() == null) {
430             Log.warn("AccessToken doesn't exist");
431             return false;
432         } else if (!tokenInfo.getAccesstoken().equals(token)) {
433             Log.warn("AccessToken is not correct");
434             return false;
435         }
436         return true;
437     }
438
439     private boolean checkExpiredTime(TokenTable tokenInfo) {
440
441         String issuedTime = tokenInfo.getIssuedtime();
442         long expiredTime = tokenInfo.getExpiredtime();
443
444         long remainTime = getElaspedSeconds(issuedTime);
445
446         if (remainTime > expiredTime) {
447             Log.warn("access token is expired");
448             return false;
449         }
450         return true;
451     }
452
453     private long getElaspedSeconds(String issuedTime) {
454
455         DateFormat format = new SimpleDateFormat("yyyyMMddkkmm");
456         Date currentTime = new Date();
457         Date issuedTimeDate = null;
458
459         try {
460             issuedTimeDate = format.parse(issuedTime);
461         } catch (ParseException e) {
462             e.printStackTrace();
463         }
464
465         long difference = currentTime.getTime() - issuedTimeDate.getTime();
466         long elaspedSeconds = difference / 1000;
467         Log.debug("accessToken elasped time: " + elaspedSeconds + "s");
468
469         return elaspedSeconds;
470     }
471
472     private HashMap<String, Object> makeRefreshTokenResponse(
473             TokenTable tokenInfo) {
474         HashMap<String, Object> response = new HashMap<>();
475         response.put(Constants.RESP_ACCESS_TOKEN, tokenInfo.getAccesstoken());
476         response.put(Constants.RESP_REFRESH_TOKEN, tokenInfo.getRefreshtoken());
477         response.put(Constants.RESP_TOKEN_TYPE, Constants.TOKEN_TYPE_BEARER);
478         response.put(Constants.RESP_EXPIRES_IN, tokenInfo.getExpiredtime());
479
480         return response;
481     }
482
483     private TokenTable requestRefreshToken(String refreshToken,
484             String authProviderName) {
485
486         if (mFactory == null) {
487             authProviderName = checkAuthProviderName(authProviderName);
488             boolean res = loadAuthProviderLibrary(authProviderName);
489
490             if (!res) {
491                 throw new InternalServerErrorException(
492                         authProviderName + " library is not loaded");
493             }
494         }
495
496         TokenTable tokenInfo = mFactory.requestRefreshTokenInfo(refreshToken);
497
498         Log.debug("access token : " + tokenInfo.getAccesstoken());
499         Log.debug("refresh token : " + tokenInfo.getRefreshtoken());
500         Log.debug("expired time : " + tokenInfo.getExpiredtime());
501
502         return tokenInfo;
503     }
504
505     private HashMap<String, Object> makeSearchUserResponse(
506             ArrayList<HashMap<String, Object>> recordList) {
507         HashMap<String, Object> response = new HashMap<>();
508         ArrayList<HashMap<String, Object>> ulist = new ArrayList<>();
509
510         for (HashMap<String, Object> record : recordList) {
511             HashMap<String, Object> uInfo = new HashMap<>();
512             String uid = record.get(Constants.KEYFIELD_UUID).toString();
513             uInfo.put(Constants.RESP_UUID, uid);
514             record.remove(Constants.KEYFIELD_UUID);
515             uInfo.putAll(record);
516             ulist.add(uInfo);
517         }
518
519         response.put(Constants.RESP_USER_LIST, ulist);
520         Log.debug("User List " + response.toString());
521
522         return response;
523     }
524
525     public HashMap<String, List<String>> getQueryMap(String queryString,
526             String splitOption) {
527         HashMap<String, List<String>> result = new HashMap<>();
528
529         ArrayList<String> parsedQuery = new ArrayList<String>(
530                 Arrays.asList(queryString.split(splitOption)));
531
532         for (String query : parsedQuery) {
533             ArrayList<String> searchType = getSearchType(query);
534
535             ArrayList<String> values = (ArrayList<String>) result
536                     .get(searchType.get(0));
537
538             if (values == null) {
539                 result.put(searchType.get(0), new ArrayList<String>(
540                         Arrays.asList(searchType.get(1))));
541             } else {
542                 values.removeAll(Arrays.asList(searchType.get(1)));
543                 values.addAll(Arrays.asList(searchType.get(1)));
544
545                 result.put(searchType.get(0), values);
546             }
547         }
548         return result;
549     }
550
551     private ArrayList<String> getSearchType(String criteria) {
552         ArrayList<String> searchType = new ArrayList<String>(
553                 Arrays.asList(criteria.split("=")));
554
555         String searchKey = searchType.get(0);
556         String searchValue = searchType.get(1);
557
558         if (searchKey == null || searchValue == null) {
559             throw new BadRequestException("search key or value is null");
560         }
561
562         return searchType;
563     }
564
565     public enum SearchOperation {
566         AND, OR
567     }
568
569     public HashMap<String, Object> searchUserUsingCriteria(
570             HashMap<String, List<String>> criteria, SearchOperation operation) {
571         ArrayList<HashMap<String, Object>> recordList = new ArrayList<>();
572         Iterator<String> keys = criteria.keySet().iterator();
573         HashMap<String, Object> condition = new HashMap<>();
574         switch (operation) {
575             case AND:
576                 while (keys.hasNext()) {
577                     String key = keys.next();
578                     List<String> searchValues = criteria.get(key);
579                     for (String searchValue : searchValues) {
580                         if (key.equals(Constants.KEYFIELD_UID)) {
581                             condition.put(Constants.KEYFIELD_UUID, searchValue);
582                         } else {
583                             condition.put(key, searchValue);
584                         }
585                     }
586                 }
587                 recordList = AccountDBManager.getInstance()
588                         .selectRecord(Constants.USER_TABLE, condition);
589                 break;
590             case OR:
591                 while (keys.hasNext()) {
592                     String key = keys.next();
593                     List<String> searchValues = criteria.get(key);
594                     for (String searchValue : searchValues) {
595                         condition = new HashMap<>();
596                         // TODO arrange "uid" and "uuid" in the DB
597                         if (key.equals(Constants.KEYFIELD_UID)) {
598                             condition.put(Constants.KEYFIELD_UUID, searchValue);
599                         } else {
600                             condition.put(key, searchValue);
601                         }
602                         ArrayList<HashMap<String, Object>> record = AccountDBManager
603                                 .getInstance()
604                                 .selectRecord(Constants.USER_TABLE, condition);
605                         recordList.removeAll(record);
606                         recordList.addAll(record);
607                     }
608                 }
609                 break;
610             default:
611                 break;
612         }
613         HashMap<String, Object> response = makeSearchUserResponse(recordList);
614         return response;
615     }
616
617     public boolean deleteDevice(String uid, String di, String accetoken) {
618
619         HashSet<String> diSet = new HashSet<String>();
620         diSet.add(di);
621
622         // token table search criteria
623         HashMap<String, Object> condition = new HashMap<>();
624         condition.put(Constants.KEYFIELD_UUID, uid);
625         condition.put(Constants.KEYFIELD_DID, di);
626
627         ArrayList<HashMap<String, Object>> recordList = AccountDBManager
628                 .getInstance().selectRecord(Constants.TOKEN_TABLE, condition);
629
630         if (recordList.isEmpty()) {
631             throw new UnAuthorizedException("access token doesn't exist");
632         }
633
634         TokenTable tokenInfo = castMapToTokenTable(recordList.get(0));
635
636         if (!verifyToken(tokenInfo, accetoken)) {
637             return false;
638         }
639
640         // delete Token information from the DB
641         AccountDBManager.getInstance().deleteRecord(Constants.TOKEN_TABLE,
642                 condition);
643         // delete device ID from all groups in the DB
644         GroupManager.getInstance().deleteDevicesFromAllGroup(di);
645
646         return true;
647     }
648 }