• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 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
16import AccountAssociation from '../bean/data/AccountAssociation';
17import AccountAssociationUtil from '../common/AssocUtil/AccountAssociationUtil';
18import AppStorageMgr from '../common/AppStorageMgr';
19import AuthorizedAccount from '../bean/data/AuthorizedAccount';
20import Constants from '../common/constant';
21import { getCurrentTime, isInvalidStr } from '../common/FileUtils/utils';
22import { HiLog } from '../common/HiLog';
23import HuksCipherUtils from '../common/huks/HuksCipherUtil';
24import StorageUtil from '../common/StorageUtil';
25import AssociationDecryptValidator from '../validator/AccountAssociation/AssociationDecryptValidator';
26import AssociationEncryptValidator from '../validator/AccountAssociation/AssociationEncryptValidator';
27
28const TAG: string = 'AssociationMgr';
29
30export default class AccountAssociationManager {
31  private static mInstance: AccountAssociationManager | null = null;
32  private storageUtil: StorageUtil | null = null;
33
34  private constructor() {
35  }
36
37  public static getInstance(): AccountAssociationManager | null {
38    const appContext = AppStorageMgr.getApplicationContext();
39    if (!appContext) {
40      HiLog.error(TAG, 'Get Application Context failed.');
41      return null;
42    }
43    if (!AccountAssociationManager.mInstance) {
44      AccountAssociationManager.mInstance = new AccountAssociationManager();
45      AccountAssociationManager.mInstance.storageUtil =
46        StorageUtil.getInstance(appContext);
47    }
48    return AccountAssociationManager.mInstance;
49  }
50
51  private async getAssociationFromLocal(ownerAccountId: string): Promise<AccountAssociation | null> {
52    HiLog.info(TAG, 'Start getting account association data from local.');
53    if (isInvalidStr(ownerAccountId)) {
54      HiLog.error(TAG, 'Owner account ID is invalid');
55      return null;
56    }
57
58    const validator = AssociationDecryptValidator.getInstance();
59    let res: AccountAssociation | null = await validator.validate(ownerAccountId);
60    if (res) {
61      HiLog.info(TAG, 'Get account association data from local success.');
62      return res;
63    }
64    HiLog.error(TAG, 'Get account association data from local failed.');
65    return null;
66  }
67
68  public async getAuthorizedAccount(ownerAccountId: string): Promise<AuthorizedAccount[]> {
69    HiLog.info(TAG, 'Enter getAuthorizedAccount.');
70
71    if (isInvalidStr(ownerAccountId)) {
72      HiLog.error(TAG, `The ownerAccountId is invalid. Code is ${Constants.ERR_CODE_ASSOCIATION_GET_FAILED}`);
73      return [];
74    }
75    let accountAssociation = await this.getAssociationFromLocal(ownerAccountId);
76    if (!accountAssociation) {
77      HiLog.info(TAG, 'No account association data exists in this device. Create a new one.');
78      accountAssociation = new AccountAssociation(ownerAccountId, []);
79    } else {
80      HiLog.info(TAG, 'The account association data has existed in this device. Decrypt and read it directly.');
81    }
82    return accountAssociation.getAuthorizedAccounts();
83  }
84
85  private async updateAuthorizedAccount(
86    ownerAccountId: string,
87    authorizedAccount: string
88  ): Promise<AuthorizedAccount[] | null> {
89    HiLog.info(TAG, 'Enter updateAuthorizedAccount.');
90
91    let currentTime: number = getCurrentTime(false);
92    let authorizedAccounts: AuthorizedAccount[] = await this.getAuthorizedAccount(ownerAccountId);
93    let accountData: AuthorizedAccount[] =
94      authorizedAccounts.filter(account => account.getUserAccount() === authorizedAccount);
95
96    if (accountData.length === 0) {
97      HiLog.info(TAG, 'No authorized account infomation. Create a new one.');
98      if (authorizedAccounts.length === Constants.ASSOCIATION_MAX_SIZE) {
99        HiLog.info(TAG,
100          `The authorized account list has reached the limit of ${Constants.ASSOCIATION_MAX_SIZE}.`);
101        HiLog.info(TAG, 'Update the authorized account list.');
102
103        let accountAssociation = new AccountAssociation(ownerAccountId, authorizedAccounts);
104        if (!accountAssociation) {
105          HiLog.error(TAG, 'Generate account association object failed.');
106          return null;
107        }
108
109        const earliestAccount = accountAssociation.removeEarliestAuthorizedAccount();
110        if (!earliestAccount) {
111          HiLog.error(TAG, 'Remove the earliest authorized account failed.');
112          return null;
113        }
114        authorizedAccounts = accountAssociation.getAuthorizedAccounts();
115      }
116      authorizedAccounts.push(new AuthorizedAccount(authorizedAccount, currentTime));
117    } else {
118      HiLog.info(TAG, 'The authorized account info already exists. Refresh the timestamp.');
119      authorizedAccounts.forEach(account => {
120        if (account.getUserAccount() === authorizedAccount) {
121          account.setTimestamp(currentTime);
122        }
123      });
124    }
125    return authorizedAccounts;
126  }
127
128  private async encryptAndSaveAssociation(
129    storageUtil: StorageUtil | null,
130    association: AccountAssociation,
131    saveFileName: string
132  ): Promise<boolean> {
133    HiLog.info(TAG, 'Start encrypting and saving account association.');
134
135    if (!storageUtil) {
136      HiLog.error(TAG, 'Storage util is null.');
137      return false;
138    }
139
140    const validator = AssociationEncryptValidator.getInstance();
141    let res: boolean = await validator.validate(storageUtil, association, saveFileName);
142    if (!res) {
143      HiLog.error(TAG, 'EncryptAndSaveAssociation failed.');
144      return false;
145    }
146    HiLog.info(TAG, 'Encrypt and save account association data success.');
147    return true;
148  }
149
150  public async setAuthorizedAccount(ownerAccountId: string, authorizedAccount: string): Promise<void> {
151    HiLog.info(TAG, 'Enter setAuthorizedAccount.');
152
153    if (isInvalidStr(ownerAccountId) || isInvalidStr(authorizedAccount)) {
154      HiLog.error(TAG,
155        `Input owerAccountId or authorizedAccount is invalid. Code is ${Constants.ERR_CODE_ASSOCIATION_SET_FAILED}`);
156      return;
157    }
158
159    try {
160      let authorizedAccounts = await this.updateAuthorizedAccount(ownerAccountId, authorizedAccount);
161      if (!authorizedAccounts) {
162        HiLog.error(TAG, `Update authorized account list failed. Code is ${Constants.ERR_CODE_ASSOCIATION_SET_FAILED}`);
163        return;
164      }
165
166      let updatedAssociation = new AccountAssociation(ownerAccountId, authorizedAccounts);
167      let associationFileName = await AccountAssociationUtil.getAccountAssociationFileName(ownerAccountId);
168      if (!associationFileName) {
169        HiLog.error(TAG, 'Get association file name failed.');
170        return;
171      }
172
173      let saveResult = await this.encryptAndSaveAssociation(this.storageUtil, updatedAssociation, associationFileName);
174      if (!saveResult) {
175        HiLog.error(TAG, 'Storage authorized account failed.');
176      }
177    } catch (error) {
178      HiLog.error(TAG, `Set authorized account error: ${JSON.stringify(error)}`);
179    }
180  }
181
182  public async deleteAuthorizedAccount(ownerAccountId: string): Promise<void> {
183    HiLog.info(TAG, 'Enter deleteAuthorizedAccount.');
184
185    if (isInvalidStr(ownerAccountId)) {
186      HiLog.error(TAG, `Input ownerAccountId is invalid. Code is ${Constants.ERR_CODE_ASSOCIATION_DELETE_FAILED}`);
187      return;
188    }
189    await HuksCipherUtils.deleteKey(Constants.ASSOCIATION_KEY_ALIAS);
190
191    let associationFileName = await AccountAssociationUtil.getAccountAssociationFileName(ownerAccountId);
192    if (!associationFileName) {
193      HiLog.error(TAG, 'Get association file name failed.');
194      return;
195    }
196    if (!this.storageUtil) {
197      HiLog.error(TAG, 'Get storage util failed.');
198      return;
199    }
200
201    await this.storageUtil.deleteData(Constants.ASSOCIATION_STORAGE_KEY, associationFileName);
202    await this.storageUtil.deleteData(Constants.AES_NONCE_KEY, associationFileName);
203    await this.storageUtil.deleteFile(associationFileName);
204  }
205}