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