1 /*
2  * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <cerrno>
16 #include <cstdio>
17 #include <fstream>
18 #include <iostream>
19 #include <unistd.h>
20 #include <vector>
21 #include "account_error_no.h"
22 #include "account_info.h"
23 #include "account_log_wrapper.h"
24 #include "directory_ex.h"
25 #include "file_ex.h"
26 #include "account_hisysevent_adapter.h"
27 #include "iinner_os_account_manager.h"
28 #include "ohos_account_data_deal.h"
29 
30 namespace OHOS {
31 namespace AccountSA {
32 namespace {
33 const std::string ACCOUNT_CFG_FILE_NAME = "/account.json";
34 const std::string ACCOUNT_AVATAR_NAME = "/account_avatar";
35 const std::string DATADEAL_JSON_KEY_OHOSACCOUNT_NAME = "account_name";
36 const std::string DATADEAL_JSON_KEY_OHOSACCOUNT_RAW_UID = "raw_uid";
37 const std::string DATADEAL_JSON_KEY_OHOSACCOUNT_UID = "open_id";
38 const std::string DATADEAL_JSON_KEY_OHOSACCOUNT_STATUS = "bind_status";
39 const std::string DATADEAL_JSON_KEY_OHOSACCOUNT_CALLINGUID = "calling_uid";
40 const std::string DATADEAL_JSON_KEY_OHOSACCOUNT_NICKNAME = "account_nickname";
41 const std::string DATADEAL_JSON_KEY_OHOSACCOUNT_AVATAR = "account_avatar";
42 const std::string DATADEAL_JSON_KEY_OHOSACCOUNT_SCALABLEDATA = "account_scalableData";
43 const std::string DATADEAL_JSON_KEY_USERID = "user_id";
44 const std::string DATADEAL_JSON_KEY_BIND_TIME = "bind_time";
45 const uint32_t ALG_COMMON_SIZE = 32;
46 } // namespace
47 
OhosAccountDataDeal(const std::string & configFileDir)48 OhosAccountDataDeal::OhosAccountDataDeal(const std::string &configFileDir)
49     : configFileDir_(configFileDir),
50     accountFileWatcherMgr_(AccountFileWatcherMgr::GetInstance())
51 {
52     accountFileOperator_ = accountFileWatcherMgr_.accountFileOperator_;
53     initOk_ = false;
54     checkCallbackFunc_ = [this](const std::string &fileName, int32_t id, uint32_t event) {
55         ACCOUNT_LOGI("inotify event = %{public}d, fileName = %{public}s", event, fileName.c_str());
56         switch (event) {
57             case IN_MODIFY: {
58                 return DealWithFileModifyEvent(fileName, id);
59             }
60             case IN_MOVE_SELF: {
61                 accountFileWatcherMgr_.RemoveFileWatcher(id, fileName);
62                 ReportOsAccountDataTampered(id, fileName, "DISTRIBUTED_ACCOUT_INFO");
63                 break;
64             }
65             case IN_DELETE_SELF: {
66                 DealWithFileDeleteEvent(fileName, id);
67                 break;
68             }
69             default: {
70                 ACCOUNT_LOGW("get event invalid!");
71                 return false;
72             }
73         }
74         return true;
75     };
76 }
77 
DealWithFileModifyEvent(const std::string & fileName,const int32_t id)78 bool OhosAccountDataDeal::DealWithFileModifyEvent(const std::string &fileName, const int32_t id)
79 {
80     ACCOUNT_LOGI("enter");
81     {
82         std::unique_lock<std::shared_timed_mutex> lock(accountFileOperator_->fileLock_);
83         if (accountFileOperator_->GetValidModifyFileOperationFlag(fileName)) {
84             ACCOUNT_LOGD("this is valid service operate, no need to deal with it.");
85             accountFileOperator_->SetValidModifyFileOperationFlag(fileName, false);
86             return true;
87         }
88     }
89     std::lock_guard<std::mutex> lock(accountInfoFileLock_);
90     std::string fileInfoStr;
91     if (accountFileOperator_->GetFileContentByPath(fileName, fileInfoStr) != ERR_OK) {
92         ACCOUNT_LOGE("get content from file %{public}s failed!", fileName.c_str());
93         return false;
94     }
95     uint8_t localDigestData[ALG_COMMON_SIZE] = {0};
96     accountFileWatcherMgr_.GetAccountInfoDigestFromFile(fileName, localDigestData, ALG_COMMON_SIZE);
97 #ifdef HAS_HUKS_PART
98     uint8_t newDigestData[ALG_COMMON_SIZE] = {0};
99     GenerateAccountInfoDigest(fileInfoStr, newDigestData, ALG_COMMON_SIZE);
100     if (memcmp(localDigestData, newDigestData, ALG_COMMON_SIZE) == 0) {
101         ACCOUNT_LOGD("No need to recover local file data.");
102         return true;
103     }
104 #endif // HAS_HUKS_PART
105     ReportOsAccountDataTampered(id, fileName, "DISTRIBUTED_ACCOUT_INFO");
106     return true;
107 }
108 
DealWithFileDeleteEvent(const std::string & fileName,const int32_t id)109 void OhosAccountDataDeal::DealWithFileDeleteEvent(const std::string &fileName, const int32_t id)
110 {
111     {
112         std::unique_lock<std::shared_timed_mutex> lock(accountFileOperator_->fileLock_);
113         if (accountFileOperator_->GetValidDeleteFileOperationFlag(fileName)) {
114             ACCOUNT_LOGD("this is valid service operate, no need to deal with it.");
115             accountFileOperator_->SetValidDeleteFileOperationFlag(fileName, false);
116             accountFileWatcherMgr_.RemoveFileWatcher(id, fileName);
117             return;
118         }
119         std::string fileDir = configFileDir_ + std::to_string(id);
120         if (!accountFileOperator_->IsExistDir(fileDir)) {
121             ACCOUNT_LOGI("this id is already removed.");
122             return;
123         }
124     }
125     ReportOsAccountDataTampered(id, fileName, "DISTRIBUTED_ACCOUT_INFO");
126 }
127 
AddFileWatcher(const int32_t id)128 void OhosAccountDataDeal::AddFileWatcher(const int32_t id)
129 {
130     std::string configFile = configFileDir_ + std::to_string(id) + ACCOUNT_CFG_FILE_NAME;
131     accountFileWatcherMgr_.AddFileWatcher(id, checkCallbackFunc_, configFile);
132 }
133 
Init(int32_t userId)134 ErrCode OhosAccountDataDeal::Init(int32_t userId)
135 {
136     std::string configFile = configFileDir_ + std::to_string(userId) + ACCOUNT_CFG_FILE_NAME;
137     if (!accountFileOperator_->IsExistFile(configFile)) {
138         ACCOUNT_LOGI("file %{public}s not exist, create!", configFile.c_str());
139         BuildJsonFileFromScratch(userId);
140     }
141 
142     std::ifstream fin(configFile);
143     if (!fin) {
144         ACCOUNT_LOGE("Failed to open config file %{public}s, errno %{public}d.", configFile.c_str(), errno);
145         ReportOhosAccountOperationFail(userId, OPERATION_INIT_OPEN_FILE_TO_READ, errno, configFile);
146         return ERR_ACCOUNT_DATADEAL_INPUT_FILE_ERROR;
147     }
148 
149     // NOT-allow exceptions when parse json file
150     std::lock_guard<std::mutex> lock(mutex_);
151     nlohmann::json jsonData = json::parse(fin, nullptr, false);
152     fin.close();
153     if (jsonData.is_discarded() || !jsonData.is_structured()) {
154         ACCOUNT_LOGE("Invalid json file, remove");
155         if (RemoveFile(configFile)) {
156             ACCOUNT_LOGE("Remove invalid json file %{public}s failed, errno %{public}d.", configFile.c_str(), errno);
157             ReportOhosAccountOperationFail(userId, OPERATION_REMOVE_FILE, errno, configFile);
158         }
159         return ERR_ACCOUNT_DATADEAL_JSON_FILE_CORRUPTION;
160     }
161 
162     // recover watch for exist account info
163     std::vector<OsAccountInfo> osAccountInfos;
164     IInnerOsAccountManager::GetInstance().QueryAllCreatedOsAccounts(osAccountInfos);
165     for (const auto &info : osAccountInfos) {
166         AddFileWatcher(info.GetLocalId());
167     }
168     initOk_ = true;
169     return ERR_OK;
170 }
171 
AccountInfoFromJson(AccountInfo & accountInfo,int32_t userId)172 ErrCode OhosAccountDataDeal::AccountInfoFromJson(AccountInfo &accountInfo, int32_t userId)
173 {
174     if (!initOk_) {
175         ACCOUNT_LOGE("not init yet!");
176         return ERR_ACCOUNT_DATADEAL_NOT_READY;
177     }
178     return GetAccountInfo(accountInfo, userId);
179 }
180 
AccountInfoToJson(const AccountInfo & accountInfo)181 ErrCode OhosAccountDataDeal::AccountInfoToJson(const AccountInfo &accountInfo)
182 {
183     if (!initOk_) {
184         ACCOUNT_LOGE("Not init ok");
185         return ERR_ACCOUNT_DATADEAL_NOT_READY;
186     }
187     return SaveAccountInfo(accountInfo);
188 }
189 
SaveAccountInfo(const AccountInfo & accountInfo)190 ErrCode OhosAccountDataDeal::SaveAccountInfo(const AccountInfo &accountInfo)
191 {
192     std::lock_guard<std::mutex> lock(accountInfoFileLock_);
193     std::string scalableDataStr = (accountInfo.ohosAccountInfo_.scalableData_).ToString();
194     nlohmann::json jsonData = json {
195         {DATADEAL_JSON_KEY_BIND_TIME, accountInfo.bindTime_},
196         {DATADEAL_JSON_KEY_USERID, accountInfo.userId_},
197         {DATADEAL_JSON_KEY_OHOSACCOUNT_NAME, accountInfo.ohosAccountInfo_.name_},
198         {DATADEAL_JSON_KEY_OHOSACCOUNT_RAW_UID, accountInfo.ohosAccountInfo_.GetRawUid()},
199         {DATADEAL_JSON_KEY_OHOSACCOUNT_UID, accountInfo.ohosAccountInfo_.uid_},
200         {DATADEAL_JSON_KEY_OHOSACCOUNT_STATUS, accountInfo.ohosAccountInfo_.status_},
201         {DATADEAL_JSON_KEY_OHOSACCOUNT_CALLINGUID, accountInfo.ohosAccountInfo_.callingUid_},
202         {DATADEAL_JSON_KEY_OHOSACCOUNT_NICKNAME, accountInfo.ohosAccountInfo_.nickname_},
203         {DATADEAL_JSON_KEY_OHOSACCOUNT_SCALABLEDATA, scalableDataStr}
204     };
205 
206     std::string avatarFile = configFileDir_ + std::to_string(accountInfo.userId_) + ACCOUNT_AVATAR_NAME;
207     ErrCode ret = accountFileOperator_->InputFileByPathAndContent(avatarFile, accountInfo.ohosAccountInfo_.avatar_);
208     if (ret != ERR_OK) {
209         ACCOUNT_LOGE("Failed to save avatar! ret = %{public}d", ret);
210         return ret;
211     }
212 
213     std::string accountInfoValue = jsonData.dump(-1, ' ', false, json::error_handler_t::ignore);
214     std::string configFile = configFileDir_ + std::to_string(accountInfo.userId_) + ACCOUNT_CFG_FILE_NAME;
215 
216     ret = accountFileOperator_->InputFileByPathAndContent(configFile, accountInfoValue);
217     if (ret == ERR_OHOSACCOUNT_SERVICE_FILE_CHANGE_DIR_MODE_ERROR) {
218         ReportOhosAccountOperationFail(accountInfo.userId_, OPERATION_CHANGE_MODE_FILE, errno, configFile);
219     }
220     if (ret != ERR_OK && ret != ERR_OHOSACCOUNT_SERVICE_FILE_CHANGE_DIR_MODE_ERROR) {
221         ReportOhosAccountOperationFail(accountInfo.userId_, OPERATION_OPEN_FILE_TO_WRITE, errno, configFile);
222     }
223     accountFileWatcherMgr_.AddAccountInfoDigest(accountInfoValue, configFile);
224     return ret;
225 }
226 
ParseJsonFromFile(const std::string & filePath,nlohmann::json & jsonData,int32_t userId)227 ErrCode OhosAccountDataDeal::ParseJsonFromFile(const std::string &filePath, nlohmann::json &jsonData, int32_t userId)
228 {
229     std::ifstream fin(filePath);
230     if (!fin) {
231         ACCOUNT_LOGE("Failed to open config file %{public}s, errno %{public}d.", filePath.c_str(), errno);
232         ReportOhosAccountOperationFail(userId, OPERATION_OPEN_FILE_TO_READ, errno, filePath);
233         return ERR_ACCOUNT_DATADEAL_INPUT_FILE_ERROR;
234     }
235     // NOT-allow exceptions when parse json file
236     jsonData = json::parse(fin, nullptr, false);
237     fin.close();
238     if (jsonData.is_discarded() || !jsonData.is_structured()) {
239         ACCOUNT_LOGE("Invalid json file,  %{public}s, remove", filePath.c_str());
240         return ERR_ACCOUNT_DATADEAL_JSON_FILE_CORRUPTION;
241     }
242     std::string avatarData;
243     auto it = jsonData.find(DATADEAL_JSON_KEY_OHOSACCOUNT_AVATAR);
244     if (it != jsonData.end()) {
245         if (it->is_string()) {
246             avatarData = it->get<std::string>();
247             jsonData[DATADEAL_JSON_KEY_OHOSACCOUNT_AVATAR] = avatarData;
248         }
249     } else {
250         std::string avatarFile = configFileDir_ + std::to_string(userId) + ACCOUNT_AVATAR_NAME;
251         if (accountFileOperator_->GetFileContentByPath(avatarFile, avatarData) == ERR_OK) {
252             jsonData[DATADEAL_JSON_KEY_OHOSACCOUNT_AVATAR] = avatarData;
253         }
254     }
255     return ERR_OK;
256 }
257 
GetAccountInfoFromJson(const nlohmann::json & jsonData,AccountInfo & accountInfo,const int32_t userId)258 ErrCode OhosAccountDataDeal::GetAccountInfoFromJson(
259     const nlohmann::json &jsonData, AccountInfo &accountInfo, const int32_t userId)
260 {
261     const auto &jsonObjectEnd = jsonData.end();
262     if ((jsonData.find(DATADEAL_JSON_KEY_BIND_TIME) != jsonObjectEnd) &&
263         (jsonData.at(DATADEAL_JSON_KEY_BIND_TIME).is_number())) {
264         accountInfo.bindTime_ = jsonData.at(DATADEAL_JSON_KEY_BIND_TIME).get<std::time_t>();
265     }
266 
267     if ((jsonData.find(DATADEAL_JSON_KEY_OHOSACCOUNT_NAME) != jsonObjectEnd) &&
268         (jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_NAME).is_string())) {
269         accountInfo.ohosAccountInfo_.name_ = jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_NAME).get<std::string>();
270     }
271 
272     if ((jsonData.find(DATADEAL_JSON_KEY_OHOSACCOUNT_RAW_UID) != jsonObjectEnd) &&
273         (jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_RAW_UID).is_string())) {
274         std::string rawUid = jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_RAW_UID).get<std::string>();
275         accountInfo.ohosAccountInfo_.SetRawUid(rawUid);
276     }
277 
278     if ((jsonData.find(DATADEAL_JSON_KEY_OHOSACCOUNT_UID) != jsonObjectEnd) &&
279         (jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_UID).is_string())) {
280         accountInfo.ohosAccountInfo_.uid_ = jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_UID).get<std::string>();
281     }
282 
283     if ((jsonData.find(DATADEAL_JSON_KEY_OHOSACCOUNT_STATUS) != jsonObjectEnd) &&
284         (jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_STATUS).is_number())) {
285         accountInfo.ohosAccountInfo_.status_ = jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_STATUS).get<int32_t>();
286     }
287 
288     if ((jsonData.find(DATADEAL_JSON_KEY_OHOSACCOUNT_CALLINGUID) != jsonObjectEnd) &&
289         (jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_CALLINGUID).is_number())) {
290         accountInfo.ohosAccountInfo_.callingUid_ =
291             jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_CALLINGUID).get<int32_t>();
292     }
293 
294     if ((jsonData.find(DATADEAL_JSON_KEY_OHOSACCOUNT_NICKNAME) != jsonObjectEnd) &&
295         (jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_NICKNAME).is_string())) {
296         accountInfo.ohosAccountInfo_.nickname_ =
297             jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_NICKNAME).get<std::string>();
298     }
299 
300     if ((jsonData.find(DATADEAL_JSON_KEY_OHOSACCOUNT_AVATAR) != jsonObjectEnd) &&
301         (jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_AVATAR).is_string())) {
302         accountInfo.ohosAccountInfo_.avatar_ = jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_AVATAR).get<std::string>();
303     }
304 
305     if ((jsonData.find(DATADEAL_JSON_KEY_OHOSACCOUNT_SCALABLEDATA) != jsonObjectEnd) &&
306         (jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_SCALABLEDATA).is_string())) {
307         auto scalableDataJson = jsonData.at(DATADEAL_JSON_KEY_OHOSACCOUNT_SCALABLEDATA).get<std::string>();
308         sptr<AAFwk::Want> want = AAFwk::Want::FromString(scalableDataJson);
309         if (want == nullptr) {
310             return ERR_ACCOUNT_COMMON_NULL_PTR_ERROR;
311         }
312         accountInfo.ohosAccountInfo_.scalableData_ = *want;
313     }
314     accountInfo.userId_ = userId;
315     return ERR_OK;
316 }
317 
GetAccountInfo(AccountInfo & accountInfo,const int32_t userId)318 ErrCode OhosAccountDataDeal::GetAccountInfo(AccountInfo &accountInfo, const int32_t userId)
319 {
320     if (userId < 0) {
321         ACCOUNT_LOGW("invalid userid = %{public}d", userId);
322         return ERR_OSACCOUNT_SERVICE_MANAGER_ID_ERROR;
323     }
324     std::string configFile = configFileDir_ + std::to_string(userId) + ACCOUNT_CFG_FILE_NAME;
325     if (!accountFileOperator_->IsExistFile(configFile)) {
326         if (errno != ENOENT) {
327             std::string errorMsg = "Stat " + configFile + " failed";
328             ReportOhosAccountOperationFail(userId, OPERATION_OPEN_FILE_TO_READ, errno, errorMsg);
329             return ERR_ACCOUNT_DATADEAL_INPUT_FILE_ERROR;
330         } else {
331             ACCOUNT_LOGI("File %{public}s not exist, create!", configFile.c_str());
332             BuildJsonFileFromScratch(userId); // create default config file for first login
333         }
334     }
335     std::lock_guard<std::mutex> lock(mutex_);
336     nlohmann::json jsonData;
337     ErrCode ret = ParseJsonFromFile(configFile, jsonData, userId);
338     if (ret != ERR_OK) {
339         return ret;
340     }
341     return GetAccountInfoFromJson(jsonData, accountInfo, userId);
342 }
343 
BuildJsonFileFromScratch(int32_t userId)344 void OhosAccountDataDeal::BuildJsonFileFromScratch(int32_t userId)
345 {
346     AccountInfo accountInfo;
347     accountInfo.userId_ = userId;
348     accountInfo.bindTime_ = 0;
349     accountInfo.ohosAccountInfo_.uid_ = DEFAULT_OHOS_ACCOUNT_UID;
350     accountInfo.ohosAccountInfo_.name_ = DEFAULT_OHOS_ACCOUNT_NAME;
351     accountInfo.ohosAccountInfo_.status_ = ACCOUNT_STATE_UNBOUND;
352     accountInfo.ohosAccountInfo_.callingUid_ = DEFAULT_CALLING_UID;
353     accountInfo.digest_ = "";
354     accountInfo.ohosAccountInfo_.SetRawUid(DEFAULT_OHOS_ACCOUNT_UID);
355     SaveAccountInfo(accountInfo);
356     AddFileWatcher(userId);
357 }
358 } // namespace AccountSA
359 } // namespace OHOS