|
|
|
@ -0,0 +1,378 @@
|
|
|
|
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
|
|
import { Injectable } from '@angular/core';
|
|
|
|
|
import { CoreWSError } from '@classes/errors/wserror';
|
|
|
|
|
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
|
|
|
|
|
import { CoreUserSummary } from '@features/user/services/user';
|
|
|
|
|
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
|
|
|
|
|
import { CoreWSExternalWarning } from '@services/ws';
|
|
|
|
|
import { makeSingleton } from '@singletons';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Service to handle data privacy.
|
|
|
|
|
*/
|
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
|
|
|
export class CoreDataPrivacyService {
|
|
|
|
|
|
|
|
|
|
static readonly ROOT_CACHE_KEY = 'CoreDataPrivacy:';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if data privacy is enabled on current site.
|
|
|
|
|
*
|
|
|
|
|
* @returns Whether data privacy is enabled.
|
|
|
|
|
*/
|
|
|
|
|
async isEnabled(): Promise<boolean> {
|
|
|
|
|
const site = CoreSites.getCurrentSite();
|
|
|
|
|
|
|
|
|
|
// Check if the privacy data WS are available in the site.
|
|
|
|
|
if (!site?.wsAvailable('tool_dataprivacy_get_data_requests')) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the user can contact the DPO, then data privacy is enabled.
|
|
|
|
|
const accessInformation = await this.getAccessInformation();
|
|
|
|
|
|
|
|
|
|
return accessInformation.cancontactdpo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get cache key for data privacy access information WS calls.
|
|
|
|
|
*
|
|
|
|
|
* @returns Cache key.
|
|
|
|
|
*/
|
|
|
|
|
protected getAccessInformationCacheKey(): string {
|
|
|
|
|
return CoreDataPrivacyService.ROOT_CACHE_KEY + 'accessInformation';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Retrieving privacy API access (permissions) information for the current user.
|
|
|
|
|
*
|
|
|
|
|
* @param options Request options.
|
|
|
|
|
* @returns Promise resolved with object with access information.
|
|
|
|
|
* @since 4.4
|
|
|
|
|
*/
|
|
|
|
|
async getAccessInformation(
|
|
|
|
|
options: CoreSitesCommonWSOptions = {},
|
|
|
|
|
): Promise<CoreDataPrivacyGetAccessInformationWSResponse> {
|
|
|
|
|
const site = await CoreSites.getSite(options.siteId);
|
|
|
|
|
|
|
|
|
|
const preSets: CoreSiteWSPreSets = {
|
|
|
|
|
cacheKey: this.getAccessInformationCacheKey(),
|
|
|
|
|
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return site.read('tool_dataprivacy_get_access_information', undefined, preSets);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Invalidates access information.
|
|
|
|
|
*
|
|
|
|
|
* @param siteId Site ID. If not defined, current site.
|
|
|
|
|
* @returns Promise resolved when the data is invalidated.
|
|
|
|
|
*/
|
|
|
|
|
protected async invalidateAccessInformation(siteId?: string): Promise<void> {
|
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
|
|
|
|
|
|
await site.invalidateWsCacheForKey(this.getAccessInformationCacheKey());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Contact the site Data Protection Officer(s).
|
|
|
|
|
*
|
|
|
|
|
* @param message Message to send to the DPO.
|
|
|
|
|
* @param siteId Site ID. If not defined, current site.
|
|
|
|
|
* @returns Promise resolved with boolean: whether the message was sent.
|
|
|
|
|
* @since 4.4
|
|
|
|
|
*/
|
|
|
|
|
async contactDPO(message: string, siteId?: string): Promise<boolean> {
|
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
|
|
|
|
|
|
const params: CoreDataPrivacyContactDPOWSParams = { message };
|
|
|
|
|
|
|
|
|
|
const response = await site.write<CoreDataPrivacyContactDPOWSResponse>('tool_dataprivacy_contact_dpo', params);
|
|
|
|
|
|
|
|
|
|
if (response.warnings && response.warnings.length) {
|
|
|
|
|
throw new CoreWSError(response.warnings[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get cache key for data requests WS calls.
|
|
|
|
|
*
|
|
|
|
|
* @returns Cache key.
|
|
|
|
|
*/
|
|
|
|
|
protected getDataRequestsCacheKey(): string {
|
|
|
|
|
return CoreDataPrivacyService.ROOT_CACHE_KEY + 'datarequests';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fetch the details of a user's data request.
|
|
|
|
|
*
|
|
|
|
|
* @param options Request options.
|
|
|
|
|
* @returns Promise resolved with the data requests.
|
|
|
|
|
* @since 4.4
|
|
|
|
|
*/
|
|
|
|
|
async getDataRequests(
|
|
|
|
|
options: CoreSitesCommonWSOptions = {},
|
|
|
|
|
): Promise<CoreDataPrivacyRequest[]> {
|
|
|
|
|
const site = await CoreSites.getSite(options.siteId);
|
|
|
|
|
|
|
|
|
|
const preSets: CoreSiteWSPreSets = {
|
|
|
|
|
cacheKey: this.getDataRequestsCacheKey(),
|
|
|
|
|
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const params: CoreDataPrivacyGetDataRequestsWSParams = {
|
|
|
|
|
userid: site.getUserId(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const response =
|
|
|
|
|
await site.read<CoreDataPrivacyGetDataRequestsWSResponse>('tool_dataprivacy_get_data_requests', params, preSets);
|
|
|
|
|
|
|
|
|
|
return response.requests;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Invalidate data requests.
|
|
|
|
|
*
|
|
|
|
|
* @param siteId Site ID. If not defined, current site.
|
|
|
|
|
* @returns Promise resolved when the data is invalidated.
|
|
|
|
|
*/
|
|
|
|
|
async invalidateDataRequests(siteId?: string): Promise<void> {
|
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
|
|
|
|
|
|
await site.invalidateWsCacheForKey(this.getDataRequestsCacheKey());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a data request.
|
|
|
|
|
*
|
|
|
|
|
* @param type Type of the request.
|
|
|
|
|
* @param comments Comments for the data request.
|
|
|
|
|
* @param siteId Site ID. If not defined, current site.
|
|
|
|
|
* @returns Promise resolved when the request is created.
|
|
|
|
|
* @since 4.4
|
|
|
|
|
*/
|
|
|
|
|
async createDataRequest(type: CoreDataPrivacyDataRequestType, comments: string, siteId?: string): Promise<number> {
|
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
|
|
|
|
|
|
const params: CoreDataPrivacyCreateDataequestWSParams = {
|
|
|
|
|
type,
|
|
|
|
|
comments,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const response =
|
|
|
|
|
await site.write<CoreDataPrivacyCreateDataRequestWSResponse>('tool_dataprivacy_create_data_request', params);
|
|
|
|
|
|
|
|
|
|
if (response.warnings && response.warnings.length) {
|
|
|
|
|
throw new CoreWSError(response.warnings[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.datarequestid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Cancel the data request made by the user.
|
|
|
|
|
*
|
|
|
|
|
* @param requestid ID of the request to cancel.
|
|
|
|
|
* @param siteId Site ID. If not defined, current site.
|
|
|
|
|
* @returns Promise resolved with boolean: whether the request was canceled.
|
|
|
|
|
* @since 4.4
|
|
|
|
|
*/
|
|
|
|
|
async cancelDataRequest(requestid: number, siteId?: string): Promise<boolean> {
|
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
|
|
|
|
|
|
const params: CoreDataPrivacyCancelDataRequestWSParams = { requestid };
|
|
|
|
|
|
|
|
|
|
const response =
|
|
|
|
|
await site.write<CoreDataPrivacyCancelDataRequestWSResponse>('tool_dataprivacy_cancel_data_request', params);
|
|
|
|
|
|
|
|
|
|
if (response.warnings && response.warnings.length) {
|
|
|
|
|
throw new CoreWSError(response.warnings[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Invalidate all the data related to data privacy.
|
|
|
|
|
*/
|
|
|
|
|
async invalidateAll(): Promise<void> {
|
|
|
|
|
await Promise.all([
|
|
|
|
|
this.invalidateAccessInformation(),
|
|
|
|
|
this.invalidateDataRequests(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if the user can cancel a request.
|
|
|
|
|
*
|
|
|
|
|
* @param request The request to check.
|
|
|
|
|
* @returns Whether the user can cancel the request.
|
|
|
|
|
*/
|
|
|
|
|
canCancelRequest(request: CoreDataPrivacyRequest): boolean {
|
|
|
|
|
const cannotCancelStatuses = [
|
|
|
|
|
CoreDataPrivacyDataRequestStatus.DATAREQUEST_STATUS_COMPLETE,
|
|
|
|
|
CoreDataPrivacyDataRequestStatus.DATAREQUEST_STATUS_DOWNLOAD_READY,
|
|
|
|
|
CoreDataPrivacyDataRequestStatus.DATAREQUEST_STATUS_DELETED,
|
|
|
|
|
CoreDataPrivacyDataRequestStatus.DATAREQUEST_STATUS_EXPIRED,
|
|
|
|
|
CoreDataPrivacyDataRequestStatus.DATAREQUEST_STATUS_CANCELLED,
|
|
|
|
|
CoreDataPrivacyDataRequestStatus.DATAREQUEST_STATUS_REJECTED,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return !cannotCancelStatuses.includes(request.status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const CoreDataPrivacy = makeSingleton(CoreDataPrivacyService);
|
|
|
|
|
|
|
|
|
|
export enum CoreDataPrivacyDataRequestType {
|
|
|
|
|
DATAREQUEST_TYPE_EXPORT = 1, // Data export request type.
|
|
|
|
|
DATAREQUEST_TYPE_DELETE = 2, // Data deletion request type.
|
|
|
|
|
DATAREQUEST_TYPE_OTHERS = 3, // Other request type. Usually of enquiries to the DPO.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export enum CoreDataPrivacyDataRequestStatus {
|
|
|
|
|
DATAREQUEST_STATUS_PENDING = 0, // Newly submitted and we haven't yet started finding out where they have data.
|
|
|
|
|
DATAREQUEST_STATUS_PREPROCESSING = 1, // Newly submitted and we have started to find the location of data.
|
|
|
|
|
DATAREQUEST_STATUS_AWAITING_APPROVAL = 2, // Metadata ready and awaiting review and approval by the Data Protection officer.
|
|
|
|
|
DATAREQUEST_STATUS_APPROVED = 3, // Request approved and will be processed soon.
|
|
|
|
|
DATAREQUEST_STATUS_PROCESSING = 4, // The request is now being processed.
|
|
|
|
|
DATAREQUEST_STATUS_COMPLETE = 5, // Information/other request completed.
|
|
|
|
|
DATAREQUEST_STATUS_CANCELLED = 6, // Data request cancelled by the user.
|
|
|
|
|
DATAREQUEST_STATUS_REJECTED = 7, // Data request rejected by the DPO.
|
|
|
|
|
DATAREQUEST_STATUS_DOWNLOAD_READY = 8, // Data request download ready.
|
|
|
|
|
DATAREQUEST_STATUS_EXPIRED = 9, // Data request expired.
|
|
|
|
|
DATAREQUEST_STATUS_DELETED = 10, // Data delete request completed, account is removed.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Data returned by tool_dataprivacy_get_access_information WS.
|
|
|
|
|
*/
|
|
|
|
|
export type CoreDataPrivacyGetAccessInformationWSResponse = {
|
|
|
|
|
cancontactdpo: boolean; // Can contact dpo.
|
|
|
|
|
canmanagedatarequests: boolean; // Can manage data requests.
|
|
|
|
|
cancreatedatadownloadrequest: boolean; // Can create data download request for self.
|
|
|
|
|
cancreatedatadeletionrequest: boolean; // Can create data deletion request for self.
|
|
|
|
|
hasongoingdatadownloadrequest: boolean; // Has ongoing data download request.
|
|
|
|
|
hasongoingdatadeletionrequest: boolean; // Has ongoing data deletion request.
|
|
|
|
|
warnings?: CoreWSExternalWarning[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Params of tool_dataprivacy_contact_dpo WS.
|
|
|
|
|
*/
|
|
|
|
|
type CoreDataPrivacyContactDPOWSParams = {
|
|
|
|
|
message: string; // The user's message to the Data Protection Officer(s).
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Data returned by tool_dataprivacy_contact_dpo WS.
|
|
|
|
|
*/
|
|
|
|
|
type CoreDataPrivacyContactDPOWSResponse = {
|
|
|
|
|
result: boolean; // The processing result
|
|
|
|
|
warnings?: CoreWSExternalWarning[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Params of tool_dataprivacy_create_data_request WS.
|
|
|
|
|
*/
|
|
|
|
|
type CoreDataPrivacyCreateDataequestWSParams = {
|
|
|
|
|
type: CoreDataPrivacyDataRequestType; // The type of data request to create. 1 for export, 2 for data deletion.
|
|
|
|
|
comments?: string; // Comments for the data request.
|
|
|
|
|
foruserid?: number; // The id of the user to create the data request for. Empty for current user.
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Data returned by tool_dataprivacy_create_data_request WS.
|
|
|
|
|
*/
|
|
|
|
|
type CoreDataPrivacyCreateDataRequestWSResponse = {
|
|
|
|
|
datarequestid: number; // The id of the created data request.
|
|
|
|
|
warnings?: CoreWSExternalWarning[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Params of tool_dataprivacy_cancel_data_request WS.
|
|
|
|
|
*/
|
|
|
|
|
type CoreDataPrivacyCancelDataRequestWSParams = {
|
|
|
|
|
requestid: number; // The request ID
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Data returned by tool_dataprivacy_cancel_data_request WS.
|
|
|
|
|
*/
|
|
|
|
|
type CoreDataPrivacyCancelDataRequestWSResponse = {
|
|
|
|
|
result: boolean; // The processing result
|
|
|
|
|
warnings?: CoreWSExternalWarning[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Params of tool_dataprivacy_get_data_requests WS.
|
|
|
|
|
*/
|
|
|
|
|
type CoreDataPrivacyGetDataRequestsWSParams = {
|
|
|
|
|
userid?: number; // The id of the user to get the data requests for. Empty for all users.
|
|
|
|
|
statuses?: CoreDataPrivacyDataRequestStatus[]; // The statuses of the data requests to get.
|
|
|
|
|
// 0 for pending 1 preprocessing, 2 awaiting approval, 3 approved,
|
|
|
|
|
// 4 processed, 5 completed, 6 cancelled, 7 rejected.
|
|
|
|
|
types?: number[]; // The types of the data requests to get. 1 for export, 2 for data deletion.
|
|
|
|
|
creationmethods?: number[]; // The creation methods of the data requests to get. 0 for manual, 1 for automatic.
|
|
|
|
|
sort?: string; // The field to sort the data requests by.
|
|
|
|
|
limitfrom?: number; // The number to start getting the data requests from.
|
|
|
|
|
limitnum?: number; // The number of data requests to get.
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Data returned by tool_dataprivacy_get_data_requests WS.
|
|
|
|
|
*/
|
|
|
|
|
type CoreDataPrivacyGetDataRequestsWSResponse = {
|
|
|
|
|
requests: CoreDataPrivacyRequest[]; // The data requests.
|
|
|
|
|
warnings?: CoreWSExternalWarning[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Data for the dataprivacy request.
|
|
|
|
|
*/
|
|
|
|
|
export type CoreDataPrivacyRequest = {
|
|
|
|
|
type: CoreDataPrivacyDataRequestType; // Type.
|
|
|
|
|
comments: string; // Comments.
|
|
|
|
|
commentsformat: number; // Commentsformat.
|
|
|
|
|
userid: number; // Userid.
|
|
|
|
|
requestedby: number; // Requestedby.
|
|
|
|
|
status: CoreDataPrivacyDataRequestStatus; // Status.
|
|
|
|
|
dpo: number; // Dpo.
|
|
|
|
|
dpocomment: string; // Dpocomment.
|
|
|
|
|
dpocommentformat: number; // Dpocommentformat.
|
|
|
|
|
systemapproved: boolean; // Systemapproved.
|
|
|
|
|
creationmethod: number; // Creationmethod.
|
|
|
|
|
id: number; // Id.
|
|
|
|
|
timecreated: number; // Timecreated.
|
|
|
|
|
timemodified: number; // Timemodified.
|
|
|
|
|
usermodified: number; // Usermodified.
|
|
|
|
|
foruser: CoreUserSummary; // The user the request is for.
|
|
|
|
|
requestedbyuser: CoreUserSummary; // The user who requested the data.
|
|
|
|
|
dpouser?: CoreUserSummary; // The user who processed the request.
|
|
|
|
|
messagehtml?: string; // Messagehtml.
|
|
|
|
|
typename: string; // Typename.
|
|
|
|
|
typenameshort: string; // Typenameshort.
|
|
|
|
|
statuslabel: string; // Statuslabel.
|
|
|
|
|
statuslabelclass: string; // Statuslabelclass.
|
|
|
|
|
canreview?: boolean; // Canreview.
|
|
|
|
|
approvedeny?: boolean; // Approvedeny.
|
|
|
|
|
allowfiltering?: boolean; // Allowfiltering.
|
|
|
|
|
canmarkcomplete?: boolean; // Canmarkcomplete.
|
|
|
|
|
};
|