1/**
2 * @file Describe the file
3 * Copyright (c) 2023 Huawei Device Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import privacyManager, { Permissions } from '@ohos.privacyManager';
18import { Log } from '@ohos/common/src/main/ets/utils/Log';
19import { hasNoError } from '@ohos/common/src/main/ets/utils/ErrorUtils';
20import {
21  BundleNameAndTokenIdFromUri,
22  getBundleNameAndTokenIDByUri
23} from '@ohos/common/src/main/ets/utils/UrlUtils';
24
25import { ErrorCode } from '@ohos/datamanager/src/main/ets/constants/ErrorCode';
26import delegate from './DataShareAbilityDelegate'
27import data_rdb from '@ohos.data.relationalStore';
28import dataSharePredicates from '@ohos.data.dataSharePredicates';
29import { BusinessError } from '@ohos.base';
30import { ValuesBucket } from '@ohos.data.ValuesBucket';
31import getTableByUri from '@ohos/datamanager/src/main/ets/utils/CalendarUriHelper';
32import { CalendarsColumns } from '@ohos/datastructure/src/main/ets/calendars/CalendarsColumns';
33import { EventColumns } from '@ohos/datastructure/src/main/ets/events/EventColumns';
34
35const TAG = 'DataShareAbilityAuthenticateProxy';
36
37const INSERT_BY_PROXY_OPERATION_NAME: string = 'insert';
38const UPDATE_BY_PROXY_OPERATION_NAME: string = 'update';
39const DELETE_BY_PROXY_OPERATION_NAME: string = 'delete';
40const QUERY_BY_PROXY_OPERATION_NAME: string = 'query';
41
42const PERMISSIONS_FLAG_HIGH: number = 2;
43const PERMISSIONS_FLAG_LOW: number = 1;
44const PERMISSIONS_FLAG_UNAUTHORIZED: number = -1;
45
46const PERMISSIONS_WRITE_WHOLE_CALENDAR: Permissions = 'ohos.permission.WRITE_WHOLE_CALENDAR';
47const PERMISSIONS_WRITE_CALENDAR: Permissions = 'ohos.permission.WRITE_CALENDAR';
48const PERMISSIONS_READ_WHOLE_CALENDAR: Permissions = 'ohos.permission.READ_WHOLE_CALENDAR';
49const PERMISSIONS_READ_CALENDAR: Permissions = 'ohos.permission.READ_CALENDAR';
50
51const SUCCESS_COUNT: number = 1;
52const FAIL_COUNT: number = 1;
53
54/**
55 * the proxy of CalendarData's DataShareAbilityDelegate, identify the high and low permissions of the app
56 *
57 * @since 2022-10-17
58 */
59class DataShareAbilityAuthenticateProxy {
60  async init(): Promise<boolean> {
61    Log.log(TAG, 'init start');
62    let isDatabaseInitComplete = await delegate.init();
63    Log.log(TAG, 'init end');
64    return isDatabaseInitComplete;
65  }
66
67  async insertByProxy(uri: string, value: data_rdb.ValuesBucket | data_rdb.ValuesBucket[],
68                      permission: Permissions, callback: Function) {
69    let insertDataParameter = new InsertDataParameter(value);
70    insertDataParameter.operationName = INSERT_BY_PROXY_OPERATION_NAME;
71    let verifyFlag = await verifyByUri(uri, insertDataParameter, PERMISSIONS_WRITE_WHOLE_CALENDAR,
72      PERMISSIONS_WRITE_CALENDAR, permission, callback);
73    dataOperateAfterVerify(uri, verifyFlag, insertDataParameter);
74  }
75
76  async deleteByProxy(uri: string, predicates: dataSharePredicates.DataSharePredicates,
77                      permission: Permissions, callback: Function) {
78    let deleteDataParameter = new DeleteDataParameter(predicates);
79    deleteDataParameter.operationName = DELETE_BY_PROXY_OPERATION_NAME;
80    let verifyFlag = await verifyByUri(uri, deleteDataParameter, PERMISSIONS_WRITE_WHOLE_CALENDAR,
81      PERMISSIONS_WRITE_CALENDAR, permission, callback);
82    dataOperateAfterVerify(uri, verifyFlag, deleteDataParameter);
83  }
84
85  async updateByProxy(uri: string, value: data_rdb.ValuesBucket,
86                      predicates: dataSharePredicates.DataSharePredicates, permission: Permissions, callback: Function) {
87    let updateDataParameter = new UpdateDataParameter(value, predicates);
88    updateDataParameter.operationName = UPDATE_BY_PROXY_OPERATION_NAME;
89    let verifyFlag = await verifyByUri(uri, updateDataParameter, PERMISSIONS_WRITE_WHOLE_CALENDAR,
90      PERMISSIONS_WRITE_CALENDAR, permission, callback);
91    dataOperateAfterVerify(uri, verifyFlag, updateDataParameter);
92  }
93
94  async queryByProxy(uri: string, columns: Array<string>,
95                     predicates: dataSharePredicates.DataSharePredicates, permission: Permissions, callback: Function) {
96    let queryDataParameter = new QueryDataParameter(columns, predicates);
97    queryDataParameter.operationName = QUERY_BY_PROXY_OPERATION_NAME;
98    let verifyFlag = await verifyByUri(uri, queryDataParameter, PERMISSIONS_READ_WHOLE_CALENDAR,
99      PERMISSIONS_READ_CALENDAR, permission, callback);
100    dataOperateAfterVerify(uri, verifyFlag, queryDataParameter);
101  }
102}
103
104/**
105 * Verify corresponding to permissions
106 *
107 * @Param uri indicates user's input uri
108 * @Param dataParameter indicates database operation information to be performed
109 * @Param highPermission indicates high-level permission to be verified
110 * @Param lowPermission indicates low-level permission to be verified
111 */
112async function verifyByUri(uri: string, dataParameter: DataParameter, highPermission: Permissions,
113                           lowPermission: Permissions, permission: Permissions, callback: Function): Promise<number> {
114  let bundleNameAndTokenIdFromUri: BundleNameAndTokenIdFromUri = getBundleNameAndTokenIDByUri(uri);
115
116  // Verify whether tokenId has HIGH-level to use database.
117  if (permission === PERMISSIONS_READ_WHOLE_CALENDAR || permission === PERMISSIONS_WRITE_WHOLE_CALENDAR) {
118    // Encapsulate the callback function of an object to add Permission Used Record
119    dataParameter.callback = (err: BusinessError, rowId: number) => {
120      callback(err, rowId);
121      if (isNeedAddPermissionUsedRecord(uri, dataParameter)) {
122        addPermissionUsedRecordInCallBack(err, bundleNameAndTokenIdFromUri.tokenId, highPermission);
123      }
124    }
125    // return high-level
126    return PERMISSIONS_FLAG_HIGH;
127  }
128
129  // Verify whether tokenId has LOW-level to use database.
130  if (permission === PERMISSIONS_READ_CALENDAR || permission === PERMISSIONS_WRITE_CALENDAR) {
131    // Encapsulate the callback function of an object to add Permission Used Record
132    dataParameter.callback = (err: BusinessError, rowId: number) => {
133      callback(err, rowId);
134      if (isNeedAddPermissionUsedRecord(uri, dataParameter)) {
135        addPermissionUsedRecordInCallBack(err, bundleNameAndTokenIdFromUri.tokenId, lowPermission);
136      }
137    }
138    // return low-level
139    return PERMISSIONS_FLAG_LOW;
140  }
141  return PERMISSIONS_FLAG_UNAUTHORIZED;
142}
143
144/**
145 * Perform database operations corresponding to permissions
146 *
147 * @Param uri indicates user's input uri
148 * @Param verifyFlag indicates the Check flag returned by permission check
149 * @Param dataParameter indicates database operation information to be performed
150 */
151function dataOperateAfterVerify(uri: string, verifyFlag: number, dataParameter: DataParameter) {
152  if (verifyFlag === PERMISSIONS_FLAG_HIGH) {
153    dataOperateSelectorByHighAuthority(uri, dataParameter);
154  } else if (verifyFlag === PERMISSIONS_FLAG_LOW) {
155    dataOperateSelectorByLowAuthority(uri, dataParameter);
156  } else {
157    // callback error with information
158    const err: BusinessError = {
159      code: ErrorCode.ILLEGAL_ARGUMENT_ERROR,
160      name: 'AuthenticateException',
161      message: 'Caller App does not have permission to read'
162    };
163    dataParameter.callback(err, PERMISSIONS_FLAG_UNAUTHORIZED);
164  }
165  return;
166}
167
168/**
169 * Perform database operations corresponding to permissions
170 *
171 * @param err Error object
172 * @Param tokenId
173 * @Param permission indicates permission to be verified
174 */
175function addPermissionUsedRecordInCallBack(err: BusinessError, tokenId: string, permission: Permissions) {
176  // add Permission Used Record
177  if (hasNoError(err)) {
178    try {
179      privacyManager.addPermissionUsedRecord(Number(tokenId), permission, SUCCESS_COUNT, 0)
180        .then(() => {
181          console.log('addPermissionUsedRecord success Permission is ' + permission);
182        })
183        .catch((err: BusinessError) => {
184          console.log(`addPermissionUsedRecord fail, err->${JSON.stringify(err)}`);
185        });
186    } catch (err) {
187      console.log(`catch err->${JSON.stringify(err)}`);
188    }
189  } else {
190    try {
191      privacyManager.addPermissionUsedRecord(Number(tokenId), permission, 0, FAIL_COUNT)
192        .then(() => {
193          console.log('addPermissionUsedRecord success Permission is ' + permission);
194        })
195        .catch((err: BusinessError) => {
196          console.log(`addPermissionUsedRecord fail, err->${JSON.stringify(err)}`);
197        });
198    } catch (err) {
199      console.log(`catch err->${JSON.stringify(err)}`);
200    }
201  }
202  return;
203}
204
205function isNeedAddPermissionUsedRecord(uri: string, dataParameter: DataParameter): boolean {
206  let table = getTableByUri(uri);
207  if (table === CalendarsColumns.TABLE_NAME) {
208    return true;
209  }
210
211  if (table !== EventColumns.TABLE_NAME) {
212    return false;
213  }
214
215  let value = dataParameter.value;
216  if (value instanceof Array) {
217    return true;
218  }
219
220  let valuesBucket: data_rdb.ValuesBucket = value as ValuesBucket;
221  let channelId = valuesBucket['channel_id'] as number;
222  if (channelId === undefined || channelId === null) {
223    return true;
224  }
225
226  if (channelId == 0) {
227    return true;
228  }
229
230  return false;
231}
232
233/**
234 * Perform database operations corresponding to high-level permissions
235 *
236 * @Param uri indicates user's input uri
237 * @Param dataParameter indicates database operation information to be performed
238 */
239async function dataOperateSelectorByHighAuthority(uri: string, dataParameter: DataParameter) {
240  let operationName = dataParameter.operationName;
241  let columns = dataParameter.columns;
242  let predicates = dataParameter.predicates;
243  let value = dataParameter.value;
244  let callback = dataParameter.callback;
245  switch (operationName) {
246    case INSERT_BY_PROXY_OPERATION_NAME:
247      if (value instanceof Array) {
248        delegate.batchInsertByHighAuthority(uri, value as ValuesBucket[], callback);
249        break;
250      }
251      delegate.insertByHighAuthority(uri, value as ValuesBucket, callback);
252      break;
253    case UPDATE_BY_PROXY_OPERATION_NAME:
254      delegate.updateByHighAuthority(uri, value as ValuesBucket, predicates, callback);
255      break;
256    case DELETE_BY_PROXY_OPERATION_NAME:
257      delegate.deleteByHighAuthority(uri, predicates, callback);
258      break;
259    case QUERY_BY_PROXY_OPERATION_NAME:
260      delegate.queryByHighAuthority(uri, columns, predicates, callback);
261      break;
262    default:
263      break;
264  }
265}
266
267/**
268 * Perform database operations corresponding to low-level permissions
269 *
270 * @Param uri indicates user's input uri
271 * @Param dataParameter indicates database operation information to be performed
272 */
273async function dataOperateSelectorByLowAuthority(uri: string, dataParameter: DataParameter) {
274  let operationName = dataParameter.operationName;
275  let columns = dataParameter.columns;
276  let predicates = dataParameter.predicates;
277  let value = dataParameter.value;
278  let callback = dataParameter.callback;
279  switch (operationName) {
280    case INSERT_BY_PROXY_OPERATION_NAME:
281      if (value instanceof Array) {
282        delegate.batchInsertByLowAuthority(uri, value as ValuesBucket[], callback);
283        break;
284      }
285      delegate.insertByLowAuthority(uri, value as ValuesBucket, callback);
286      break;
287    case UPDATE_BY_PROXY_OPERATION_NAME:
288      delegate.updateByLowAuthority(uri, value as ValuesBucket, predicates, callback);
289      break;
290    case DELETE_BY_PROXY_OPERATION_NAME:
291      delegate.deleteByLowAuthority(uri, predicates, callback);
292      break;
293    case QUERY_BY_PROXY_OPERATION_NAME:
294      delegate.queryByLowAuthority(uri, columns, predicates, callback);
295      break;
296    default:
297      break;
298  }
299}
300
301/**
302 * List of operation parameters for database operation
303 *
304 * @since 2022-12-28
305 */
306abstract class DataParameter {
307  public operationName: string = '';
308
309  public value: data_rdb.ValuesBucket | data_rdb.ValuesBucket[] = {};
310
311  public columns: Array<string> = [];
312
313  public predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
314
315  public callback: Function = (err: BusinessError, count: number) => {
316  };
317}
318
319/**
320 * List of operation parameters for insert operation
321 *
322 * @since 2022-12-28
323 */
324class InsertDataParameter extends DataParameter {
325  constructor(value: data_rdb.ValuesBucket | data_rdb.ValuesBucket[]) {
326    super();
327    this.value = value;
328  }
329}
330
331/**
332 * List of operation parameters for delete operation
333 *
334 * @since 2022-12-28
335 */
336class DeleteDataParameter extends DataParameter {
337  constructor(predicates: dataSharePredicates.DataSharePredicates) {
338    super();
339    this.predicates = predicates;
340  }
341}
342
343/**
344 * List of operation parameters for update operation
345 *
346 * @since 2022-12-28
347 */
348class UpdateDataParameter extends DataParameter {
349  constructor(value: data_rdb.ValuesBucket, predicates: dataSharePredicates.DataSharePredicates) {
350    super();
351    this.value = value;
352    this.predicates = predicates;
353  }
354}
355
356/**
357 * List of operation parameters for query operation
358 *
359 * @since 2022-10-28
360 */
361class QueryDataParameter extends DataParameter {
362  constructor(columns: Array<string>, predicates: dataSharePredicates.DataSharePredicates) {
363    super();
364    this.columns = columns;
365    this.predicates = predicates;
366  }
367}
368
369export default new DataShareAbilityAuthenticateProxy()