296669ab53d515d382d72580c1fc82d7e2184ed1
[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.IOException;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.nio.file.Paths;
28 import java.text.DateFormat;
29 import java.text.ParseException;
30 import java.text.SimpleDateFormat;
31 import java.util.ArrayList;
32 import java.util.Date;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.UUID;
36
37 import org.iotivity.cloud.accountserver.Constants;
38 import org.iotivity.cloud.accountserver.db.AccountDBManager;
39 import org.iotivity.cloud.accountserver.db.TokenTable;
40 import org.iotivity.cloud.accountserver.db.UserTable;
41 import org.iotivity.cloud.accountserver.oauth.OAuthProviderFactory;
42 import org.iotivity.cloud.accountserver.resources.acl.group.GroupBrokerManager;
43 import org.iotivity.cloud.accountserver.resources.acl.group.GroupManager;
44 import org.iotivity.cloud.accountserver.resources.acl.id.AclResource;
45 import org.iotivity.cloud.accountserver.util.TypeCastingManager;
46 import org.iotivity.cloud.base.exception.ServerException.BadRequestException;
47 import org.iotivity.cloud.base.exception.ServerException.InternalServerErrorException;
48 import org.iotivity.cloud.base.exception.ServerException.NotFoundException;
49 import org.iotivity.cloud.base.exception.ServerException.UnAuthorizedException;
50 import org.iotivity.cloud.util.Log;
51
52 /**
53  *
54  * This class provides a set of APIs to handle requests about account
55  * information of authorized user.
56  *
57  */
58 public class AccountManager {
59
60     private OAuthProviderFactory           mFactory                  = null;
61     private TypeCastingManager<UserTable>  mUserTableCastingManager  = new TypeCastingManager<>();
62     private TypeCastingManager<TokenTable> mTokenTableCastingManager = new TypeCastingManager<>();
63
64     /**
65      * API to return a sign-up response payload
66      * 
67      * @param did
68      *            Device id registered under user account
69      * @param authCode
70      *            Unique identifier of the resource which is obtained from an
71      *            auth provider or a single sign-on (SSO) client
72      * @param authProvider
73      *            Provider name user for authentication (e.g., "Github")
74      * @param options
75      *            Optional field (e.g., region authserver url, apiserver url)
76      * 
77      * @return Sign-up response payload
78      */
79
80     public HashMap<String, Object> signUp(String did, String authCode,
81             String authProvider, Object options) {
82         boolean res = false;
83
84         // check auth provider name not to be case-sensitive
85         authProvider = checkAuthProviderName(authProvider);
86         res = loadAuthProviderLibrary(authProvider);
87
88         if (!res) {
89             throw new InternalServerErrorException(
90                     authProvider + " library is not loaded");
91         }
92         String userUuid = null;
93         // set token data
94         TokenTable tokenInfo = requestAccessToken(authCode, options);
95         tokenInfo.setDid(did);
96         tokenInfo.setProvider(authProvider);
97         Date currentTime = new Date();
98         DateFormat transFormat = new SimpleDateFormat("yyyyMMddkkmm");
99         tokenInfo.setIssuedtime(transFormat.format(currentTime));
100
101         // set user data
102         UserTable userInfo = requestUserInfo(tokenInfo.getAccesstoken(),
103                 options);
104         userInfo.setProvider(authProvider);
105
106         // check uuid
107         userUuid = findUuid(userInfo.getUserid(), authProvider);
108
109         // store token information and user information to the DB
110         // private group creation and store group information to the DB
111         userUuid = storeUserTokenInfo(userUuid, userInfo, tokenInfo, did);
112
113         AclResource.getInstance().createAcl(userUuid, did);
114
115         // make response
116         HashMap<String, Object> response = makeSignUpResponse(tokenInfo);
117
118         return response;
119     }
120
121     /**
122      * API to return a sign-in or sign-out response payload
123      * 
124      * @param uuid
125      *            User id which is provided by Sign-up process
126      * @param did
127      *            Device id registered under user account
128      * @param accessToken
129      *            Access token used for communication with cloud
130      * @return Sign-in or sign-out response payload
131      */
132     public HashMap<String, Object> signInOut(String uuid, String did,
133             String accessToken) {
134
135         // find token information corresponding to the uuid and did
136         HashMap<String, Object> condition = new HashMap<>();
137         condition.put(Constants.KEYFIELD_UUID, uuid);
138
139         ArrayList<HashMap<String, Object>> recordList = findRecord(
140                 AccountDBManager.getInstance()
141                         .selectRecord(Constants.TOKEN_TABLE, condition),
142                 Constants.KEYFIELD_DID, did);
143
144         if (recordList.isEmpty()) {
145             throw new UnAuthorizedException("access token doesn't exist");
146         }
147
148         HashMap<String, Object> record = recordList.get(0);
149
150         TokenTable tokenInfo = castMapToTokenTable(record);
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 authProvider) {
243
244         String authProviderName = null;
245
246         if (authProvider.equalsIgnoreCase(Constants.GITHUB)) {
247             authProviderName = Constants.GITHUB;
248         } else if (authProvider.equalsIgnoreCase(Constants.SAMSUNG)) {
249             authProviderName = Constants.SAMSUNG;
250         } else if (authProvider.equalsIgnoreCase(Constants.GOOGLE))
251             authProviderName = Constants.GOOGLE;
252         else {
253             Log.w("Unsupported oauth provider : " + authProvider);
254         }
255
256         return authProviderName;
257     }
258
259     private String findUuid(String userId, String authProvider) {
260         String uuid = null;
261
262         HashMap<String, Object> condition = new HashMap<>();
263         condition.put(Constants.KEYFIELD_USERID, userId);
264
265         ArrayList<HashMap<String, Object>> recordList = AccountDBManager
266                 .getInstance().selectRecord(Constants.USER_TABLE, condition);
267
268         for (HashMap<String, Object> record : recordList) {
269             String foundProvider = record.get(Constants.KEYFIELD_PROVIDER)
270                     .toString();
271             if (foundProvider != null
272                     && foundProvider.equalsIgnoreCase(authProvider)) {
273                 return record.get(Constants.KEYFIELD_UUID).toString();
274             }
275         }
276         return uuid;
277     }
278
279     private HashMap<String, Object> castUserTableToMap(UserTable userInfo) {
280
281         return mUserTableCastingManager.convertObjectToMap(userInfo);
282     }
283
284     private HashMap<String, Object> castTokenTableToMap(TokenTable tokenInfo) {
285
286         return mTokenTableCastingManager.convertObjectToMap(tokenInfo);
287     }
288
289     private TokenTable castMapToTokenTable(HashMap<String, Object> record) {
290         TokenTable tokenInfo = new TokenTable();
291         return mTokenTableCastingManager.convertMaptoObject(record, tokenInfo);
292     }
293
294     private HashMap<String, Object> makeSignUpResponse(TokenTable tokenInfo) {
295
296         HashMap<String, Object> response = new HashMap<>();
297
298         response.put(Constants.RESP_ACCESS_TOKEN, tokenInfo.getAccesstoken());
299         response.put(Constants.RESP_REFRESH_TOKEN, tokenInfo.getRefreshtoken());
300         response.put(Constants.RESP_TOKEN_TYPE, Constants.TOKEN_TYPE_BEARER);
301         response.put(Constants.RESP_EXPIRES_IN, tokenInfo.getExpiredtime());
302         response.put(Constants.RESP_UUID, tokenInfo.getUuid());
303
304         // It will be modified.
305         response.put(Constants.RESP_REDIRECT_URI, getRegionCIUrl());
306         response.put(Constants.RESP_CERTIFICATE, getRootCert());
307         response.put(Constants.RESP_SERVER_ID, Constants.CLOUD_UUID);
308
309         return response;
310     }
311
312     private String getRegionCIUrl() {
313
314         // TODO: add region management
315         return "coap+tcp://127.0.0.1:5683";
316     }
317
318     private byte[] getRootCert() {
319
320         byte[] byteRootCert = null;
321
322         Path path = Paths.get(Constants.ROOT_CERT_FILE);
323
324         try {
325
326             byteRootCert = Files.readAllBytes(path);
327
328         } catch (IOException e) {
329
330             e.printStackTrace();
331             // throw new InternalServerErrorException(
332             // "root cert file read failed!");
333         }
334
335         return byteRootCert;
336     }
337
338     private Boolean loadAuthProviderLibrary(String authProvider) {
339         mFactory = new OAuthProviderFactory();
340
341         return mFactory.load(authProvider);
342     }
343
344     private TokenTable requestAccessToken(String authCode, Object options) {
345         TokenTable tokenInfo = mFactory.requestAccessTokenInfo(authCode,
346                 options);
347         Log.d("access token : " + tokenInfo.getAccesstoken());
348         Log.d("refresh token : " + tokenInfo.getRefreshtoken());
349         Log.d("expired time : " + tokenInfo.getExpiredtime());
350
351         return tokenInfo;
352     }
353
354     private UserTable requestUserInfo(String accessToken, Object options) {
355         UserTable userInfo = mFactory.requestGetUserInfo(accessToken, options);
356         Log.d("user id  : " + userInfo.getUserid());
357
358         return userInfo;
359     }
360
361     private String generateUuid() {
362         UUID uuid = UUID.randomUUID();
363         String userUuid = uuid.toString();
364         Log.d("generated uuid : " + userUuid);
365         return userUuid;
366     }
367
368     private ArrayList<HashMap<String, Object>> findRecord(
369             ArrayList<HashMap<String, Object>> recordList, String fieldName,
370             String value) {
371         ArrayList<HashMap<String, Object>> foundRecord = new ArrayList<>();
372
373         for (HashMap<String, Object> record : recordList) {
374             Object obj = record.get(fieldName);
375             if (obj != null && obj.equals(value)) {
376                 foundRecord.add(record);
377             }
378         }
379         return foundRecord;
380     }
381
382     private HashMap<String, Object> makeSignInResponse(long remainedSeconds) {
383         HashMap<String, Object> response = new HashMap<>();
384         response.put(Constants.RESP_EXPIRES_IN, remainedSeconds);
385
386         return response;
387     }
388
389     private long getRemainedSeconds(long expiredTime, String issuedTime) {
390         if (expiredTime == Constants.TOKEN_INFINITE) {
391             return Constants.TOKEN_INFINITE;
392         } else {
393             return expiredTime - getElaspedSeconds(issuedTime);
394         }
395     }
396
397     private boolean verifyToken(TokenTable tokenInfo, String accessToken) {
398
399         if (!checkAccessTokenInDB(tokenInfo, accessToken)) {
400             return false;
401         }
402
403         if (tokenInfo.getExpiredtime() != Constants.TOKEN_INFINITE
404                 && !checkExpiredTime(tokenInfo)) {
405             return false;
406         }
407
408         return true;
409     }
410
411     private boolean checkRefreshTokenInDB(TokenTable tokenInfo, String token) {
412         if (tokenInfo.getRefreshtoken() == null) {
413             Log.w("Refreshtoken doesn't exist");
414             return false;
415         } else if (!tokenInfo.getRefreshtoken().equals(token)) {
416             Log.w("Refreshtoken is not correct");
417             return false;
418         }
419         return true;
420     }
421
422     private boolean checkAccessTokenInDB(TokenTable tokenInfo, String token) {
423         if (tokenInfo.getAccesstoken() == null) {
424             Log.w("AccessToken doesn't exist");
425             return false;
426         } else if (!tokenInfo.getAccesstoken().equals(token)) {
427             Log.w("AccessToken is not correct");
428             return false;
429         }
430         return true;
431     }
432
433     private boolean checkExpiredTime(TokenTable tokenInfo) {
434
435         String issuedTime = tokenInfo.getIssuedtime();
436         long expiredTime = tokenInfo.getExpiredtime();
437
438         long remainTime = getElaspedSeconds(issuedTime);
439
440         if (remainTime > expiredTime) {
441             Log.w("access token is expired");
442             return false;
443         }
444         return true;
445     }
446
447     private long getElaspedSeconds(String issuedTime) {
448
449         DateFormat format = new SimpleDateFormat("yyyyMMddkkmm");
450         Date currentTime = new Date();
451         Date issuedTimeDate = null;
452
453         try {
454             issuedTimeDate = format.parse(issuedTime);
455         } catch (ParseException e) {
456             e.printStackTrace();
457         }
458
459         long difference = currentTime.getTime() - issuedTimeDate.getTime();
460         long elaspedSeconds = difference / 1000;
461         Log.d("accessToken elasped time: " + elaspedSeconds + "s");
462
463         return elaspedSeconds;
464     }
465
466     private HashMap<String, Object> makeRefreshTokenResponse(
467             TokenTable tokenInfo) {
468         HashMap<String, Object> response = new HashMap<>();
469         response.put(Constants.RESP_ACCESS_TOKEN, tokenInfo.getAccesstoken());
470         response.put(Constants.RESP_REFRESH_TOKEN, tokenInfo.getRefreshtoken());
471         response.put(Constants.RESP_TOKEN_TYPE, Constants.TOKEN_TYPE_BEARER);
472         response.put(Constants.RESP_EXPIRES_IN, tokenInfo.getExpiredtime());
473
474         return response;
475     }
476
477     private TokenTable requestRefreshToken(String refreshToken,
478             String provider) {
479
480         if (mFactory == null) {
481
482             boolean res = false;
483             String authProvider = checkAuthProviderName(provider);
484             res = loadAuthProviderLibrary(authProvider);
485
486             if (!res) {
487                 throw new InternalServerErrorException(
488                         authProvider + " library is not loaded");
489             }
490         }
491
492         TokenTable tokenInfo = mFactory.requestRefreshTokenInfo(refreshToken);
493
494         Log.d("access token : " + tokenInfo.getAccesstoken());
495         Log.d("refresh token : " + tokenInfo.getRefreshtoken());
496         Log.d("expired time : " + tokenInfo.getExpiredtime());
497
498         return tokenInfo;
499     }
500
501     public HashMap<String, Object> searchUserAboutUuid(String uuid) {
502         // search user info about uuid
503         HashMap<String, Object> condition = new HashMap<>();
504         condition.put(Constants.KEYFIELD_UUID, uuid);
505
506         ArrayList<HashMap<String, Object>> recordList = AccountDBManager
507                 .getInstance().selectRecord(Constants.USER_TABLE, condition);
508         HashMap<String, Object> response = makeSearchUserResponse(recordList);
509
510         return response;
511     }
512
513     private HashMap<String, Object> makeSearchUserResponse(
514             ArrayList<HashMap<String, Object>> recordList) {
515         HashMap<String, Object> response = new HashMap<>();
516         ArrayList<HashMap<String, Object>> ulist = new ArrayList<>();
517
518         for (HashMap<String, Object> record : recordList) {
519             HashMap<String, Object> uInfo = new HashMap<>();
520             String uid = record.get(Constants.KEYFIELD_UUID).toString();
521             uInfo.put(Constants.RESP_UUID, uid);
522             record.remove(Constants.KEYFIELD_UUID);
523             uInfo.put(Constants.RESP_USER_INFO, record);
524             ulist.add(uInfo);
525         }
526
527         response.put(Constants.RESP_USER_LIST, ulist);
528         Log.d("User List " + response.toString());
529
530         return response;
531     }
532
533     // TODO: It will be changed
534     public HashMap<String, Object> searchUserAboutCriteria(String criteria) {
535         // parse criteria
536         String[] searchType = getSearchType(criteria);
537
538         // search user info about criteria
539         HashMap<String, Object> condition = new HashMap<>();
540         condition.put(searchType[0], searchType[1]);
541
542         ArrayList<HashMap<String, Object>> recordList = AccountDBManager
543                 .getInstance().selectRecord(Constants.USER_TABLE, condition);
544         HashMap<String, Object> response = makeSearchUserResponse(recordList);
545         return response;
546     }
547
548     // TODO: It will be changed
549     private String[] getSearchType(String criteria) {
550         String[] searchType = criteria.split(":");
551         String searchKey = searchType[0];
552         String searchValue = searchType[1];
553
554         if (searchKey == null || searchValue == null) {
555             throw new BadRequestException("search key or value is null");
556         }
557
558         return searchType;
559     }
560
561     public void deleteDevice(String uid, String di) {
562
563         HashSet<String> diSet = new HashSet<String>();
564         diSet.add(di);
565
566         // token table search criteria
567         HashMap<String, Object> condition = new HashMap<>();
568         condition.put(Constants.KEYFIELD_UUID, uid);
569         condition.put(Constants.KEYFIELD_DID, di);
570
571         // delete Token information from the DB
572         AccountDBManager.getInstance().deleteRecord(Constants.TOKEN_TABLE,
573                 condition);
574         // delete device ID from all groups in the DB
575         GroupManager.getInstance().deleteDevicesFromAllGroup(di);
576     }
577
578 }