MOBILE-4329 dataprivacy: Add module structure with WS calls

main
Pau Ferrer Ocaña 2024-02-02 12:56:03 +01:00
parent b38223bb59
commit 84d83b0450
6 changed files with 489 additions and 0 deletions

View File

@ -0,0 +1,16 @@
// (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.
// Routing.
export const CORE_DATAPRIVACY_PAGE_NAME = 'dataprivacy';

View File

@ -0,0 +1,31 @@
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
import { CoreUserDelegate } from '@features/user/services/user-delegate';
import { CoreDataPrivacyUserHandler } from './services/handlers/user';
@NgModule({
providers: [
{
provide: APP_INITIALIZER,
multi: true,
useValue: () => {
CoreUserDelegate.registerHandler(CoreDataPrivacyUserHandler.instance);
},
},
],
})
export class CoreDataPrivacyModule {}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Data privacy"
}

View File

@ -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.
};

View File

@ -0,0 +1,59 @@
// (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 { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
import { CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons';
import { CoreDataPrivacy } from '../dataprivacy';
import { CORE_DATAPRIVACY_PAGE_NAME } from '@features/dataprivacy/constants';
/**
* Handler to visualize custom reports.
*/
@Injectable({ providedIn: 'root' })
export class CoreDataPrivacyUserHandlerService implements CoreUserProfileHandler {
protected pageName = CORE_DATAPRIVACY_PAGE_NAME;
type = CoreUserDelegateService.TYPE_NEW_PAGE;
name = 'CoreDataPrivacyDelegate';
priority = 100;
/**
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return await CoreDataPrivacy.isEnabled();
}
/**
* @inheritdoc
*/
getDisplayData(): CoreUserProfileHandlerData {
return {
class: 'core-data-privacy',
icon: 'fas-user-shield',
title: 'core.dataprivacy.pluginname',
action: async (event): Promise<void> => {
event.preventDefault();
event.stopPropagation();
await CoreNavigator.navigateToSitePath(this.pageName);
},
};
}
}
export const CoreDataPrivacyUserHandler = makeSingleton(CoreDataPrivacyUserHandlerService);

View File

@ -19,6 +19,7 @@ import { CoreCommentsModule } from './comments/comments.module';
import { CoreContentLinksModule } from './contentlinks/contentlinks.module';
import { CoreCourseModule } from './course/course.module';
import { CoreCoursesModule } from './courses/courses.module';
import { CoreDataPrivacyModule } from './dataprivacy/dataprivacy.module';
import { CoreEditorModule } from './editor/editor.module';
import { CoreEmulatorModule } from './emulator/emulator.module';
import { CoreEnrolModule } from './enrol/enrol.module';
@ -53,6 +54,7 @@ import { CoreReportBuilderModule } from './reportbuilder/reportbuilder.module';
CoreContentLinksModule,
CoreCourseModule,
CoreCoursesModule,
CoreDataPrivacyModule,
CoreEditorModule,
CoreEnrolModule,
CoreFileUploaderModule,