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()