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