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