packages/oae-principals/lib/definitive-deletion.js
/*!
* Copyright 2017 Apereo Foundation (AF) Licensed under the
* Educational Community 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://opensource.org/licenses/ECL-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 fs from 'node:fs';
import _ from 'underscore';
import async from 'async';
import shortId from 'shortid';
import { addMonths } from 'date-fns';
import * as UIAPI from 'oae-ui/lib/api.js';
import * as AuthenticationAPI from 'oae-authentication/lib/api.js';
import * as ActivityAPI from 'oae-activity/lib/api.js';
import * as AuthzAPI from 'oae-authz';
import * as AuthzInvitationDAO from 'oae-authz/lib/invitations/dao.js';
import { AuthzConstants } from 'oae-authz/lib/constants.js';
import * as AuthzDelete from 'oae-authz/lib/delete.js';
import * as ContentAPI from 'oae-content';
import * as ContentUtil from 'oae-content/lib/internal/util.js';
import * as DiscussionAPI from 'oae-discussions/lib/api.discussions.js';
import * as EmailAPI from 'oae-email';
import * as FolderAPI from 'oae-folders';
import * as FollowingAPI from 'oae-following/lib/api.js';
import * as MeetingsAPI from 'oae-jitsi';
import * as TenantsUtil from 'oae-tenants/lib/util.js';
import { setUpConfig } from 'oae-config';
import { logger } from 'oae-logger';
import * as GroupAPI from './api.group.js';
import * as PrincipalsAPI from './api.user.js';
import { PrincipalsConstants } from './constants.js';
import * as PrincipalsDAO from './internal/dao.js';
import PrincipalsEmitter from './internal/emitter.js';
const PrincipalsConfig = setUpConfig('oae-principals');
const log = logger('oae-principals');
const FOLDER = 'folder';
const CONTENT = 'content';
const DISCUSSION = 'discussion';
const MEETING = 'meeting';
const GROUP = 'group';
const PUBLIC = 'public';
const DELETE = 'delete';
const RESOURCE_TYPES = [CONTENT, FOLDER, DISCUSSION, MEETING, GROUP];
const FOLDER_PREFIX = 'f';
const CONTENT_PREFIX = 'c';
const DISCUSSION_PREFIX = 'd';
const MEETING_PREFIX = 'm';
const GROUP_PREFIX = 'g';
/**
* Get or create user archive
*
* @param {Context} ctx Current execution context
* @param {Object} user User to be archived
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @param {Object} callback.userArchive Return user archive
*/
const fetchOrCloneFromUser = (ctx, user, callback) => {
PrincipalsDAO.getArchivedUser(user.tenant.alias, (error, userClone) => {
if (error) return callback(error);
if (userClone) return callback(null, userClone);
// Persist the user object
const userOptions = {
tenant: { alias: user.tenant.alias },
visibility: PUBLIC,
emailPreference: 'never',
locale: ctx.locale(),
acceptedTC: null,
isUserArchive: 'true'
};
const displayName = user.tenant.alias + ' archive';
// Create a record in the principals table
PrincipalsAPI.createUser(ctx, user.tenant.alias, displayName, userOptions, (error, userClone) => {
if (error) return callback(error);
// Create a user archive in the table archiveByTenant
PrincipalsDAO.createArchivedUser(user.tenant.alias, userClone.id, (error_) => {
if (error_) return callback(error_);
// Get and return the userArchive
PrincipalsDAO.getArchivedUser(user.tenant.alias, (error, userClone) => {
if (error) return callback(error);
return callback(null, userClone);
});
});
});
});
};
/**
* Delete rights on a user + update roles on editors && on user archive
*
* @param {Context} ctx Current execution context
* @param {String} user User that will be deleted
* @param {String} cloneUsers Users Archive
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @param {Object} callback.listEmail Array of users to email
*/
const transferPermissionsToCloneUser = (ctx, user, cloneUsers, callback) => {
callback = callback || function () {};
const listEmail = [];
const listOfIdElementToBeTransferred = [];
async.eachSeries(
RESOURCE_TYPES,
(resourceType, done) => {
_transferResourcePermissions(
ctx,
user,
cloneUsers,
listEmail,
listOfIdElementToBeTransferred,
resourceType,
(error /* ListEmail, listOfIdElementToBeTransferred */) => {
if (error) return done(error);
return done();
}
);
},
() => {
_addToArchive(cloneUsers.archiveId, user, listOfIdElementToBeTransferred, (error) => {
if (error) return callback(error);
_sendEmail(ctx, listEmail, cloneUsers, user, (error, listEmail) => {
if (error) return callback(error);
return callback(null, listEmail);
});
});
}
);
};
/**
* Create a member list with corresponding resources
*
* values of deleted :
* contentWillBeDeleted : list of resources - corresponding to a user - which will be delete into X months
* userJustLeaving : list of resources - corresponding to a user - where the user deleted is leaving these resources
*
* @param {Array} list List of resources corresponding to a user
* @param {Object} resource The resource to add to the list
* @param {Array} memberList The list of memeber to add to the list
* @param {Array} action The action can be 'true' if the content will be deleted into X month or 'false' if
* the deleted user is just leaving the resource.
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occurred, if any
*/
const _addMemberToList = (list, resource, memberList, action, callback) => {
if (_.isEmpty(memberList)) {
return callback();
}
list = list || [];
async.eachSeries(
memberList,
(member, done) => {
_parseMember(list, resource, member, action, (error, newList) => {
if (error) return done(error);
list = newList;
return done();
});
},
() => callback(null, list)
);
};
const _parseMember = (list, resource, user, action, callback) => {
if (_.isEmpty(list)) {
list.push({ contentWillBeDeleted: [], userJustLeaving: [], profile: user.profile });
}
async.eachSeries(
list,
(member, done) => {
if (user.profile.id === member.profile.id) {
if (action === true) {
member.contentWillBeDeleted.push(resource);
} else {
member.userJustLeaving.push(resource);
}
return done(true);
}
return done();
},
(isUserFound) => {
if (!isUserFound) {
if (action === true) {
list.push({ contentWillBeDeleted: [resource], userJustLeaving: [], profile: user.profile });
} else {
list.push({ contentWillBeDeleted: [], userJustLeaving: [resource], profile: user.profile });
}
}
return callback(null, list);
}
);
};
/**
* Send an email token to a user that have a resource shared with the deleted user
*
* @param {Context} ctx Current execution context
* @param {Object} data User to send mail + resource
* @param {Object} cloneUser The archive user
* @param {Object} userDeleted The user deleted
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occurred, if any
*/
const _sendEmail = (ctx, data, cloneUser, userDeleted, callback) => {
callback =
callback ||
function (error) {
if (error) {
log().error({ err: error, user: userDeleted.id }, 'Unable to send a user a verification email');
}
};
if (_.isEmpty(data)) {
return callback();
}
PrincipalsDAO.getPrincipalSkipCache(cloneUser.archiveId, (error, cloneUser) => {
if (error) return callback(error);
// Grab the configuration field. This will return the number of months
const month = PrincipalsConfig.getValue(ctx.tenant().alias, 'user', DELETE);
if (_.isEmpty(data)) {
return callback();
}
const listEmail = [];
async.eachSeries(
data,
(user, done) => {
if (_.isEmpty(user.contentWillBeDeleted) && _.isEmpty(user.userJustLeaving)) {
return done();
}
const token = shortId.generate();
PrincipalsDAO.storeEmailToken(user.profile.id, user.profile.email, token, (error_) => {
if (error_) return callback(error_);
const resource = _.omit(user, 'profile');
user = _.omit(user, ['contentWillBeDeleted', 'userJustLeaving']);
// The EmailAPI expects a user to have a verified email address. As this is not the case
// when sending an email token, we send in a patched user object
const userToEmail = _.extend({}, user, { email: user.profile.email });
// Send an email to the specified e-mail address
const dataEmail = {
tenant: ctx.tenant(),
userDeletedName: userDeleted.displayName,
resource,
user,
month,
baseUrl: TenantsUtil.getBaseUrl(ctx.tenant()),
skinVariables: UIAPI.getTenantSkinVariables(ctx.tenant().alias),
token,
archiveEmail: cloneUser.email
};
// We pass the current date in as the 'hashCode' for this email. We need to be able to send
// a copy of the same email for the 'resend email token' functionality. As we don't expect
// that this logic will get stuck in a loop this is probably OK
EmailAPI.sendEmail('oae-principals', 'notify', userToEmail.profile, dataEmail, { hash: Date.now() });
listEmail.push(user.profile.email);
return done();
});
},
() => callback(null, listEmail)
);
});
};
/**
* Delete rights on element + update roles on editors && on user archive
*
* @param {Context} ctx Current execution context
* @param {String} user The user to delete
* @param {String} archiveUser User Archive
* @param {String} listEmail Array of users to email
* @param {String} listOfIdElementToBeTransferred List of element to remove from deleted user
* @param {String} resourceType Type can be : 'content', 'folder', 'discussion', 'meeting' or 'group'
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @param {Array} callback.listEmail Array of users to email
* @param {Array} callback.listOfIdElementToBeTransferred List of element to remove from deleted user
* @api private
*/
const _transferResourcePermissions = (
ctx,
user,
archiveUser,
listElementByMember,
listOfIdElementToBeTransferred,
resourceType,
callback
) => {
_getLibrary(ctx, user.id, resourceType, (error, libraryContents) => {
if (error) return callback(error);
if (_.isEmpty(libraryContents)) {
return callback(null, listElementByMember, listOfIdElementToBeTransferred);
}
async.mapSeries(
libraryContents,
(eachLibraryItem, done) => {
// Search if other user have right on this document
_getMembers(ctx, eachLibraryItem.id, resourceType, (error, memberList) => {
if (error) return callback(error);
// If he's not a manager, do nothing
_isManagerOfContent(user.id, eachLibraryItem, resourceType, (error, isManager) => {
if (error) return callback(error);
// Remove the deleted user from the member list
_removeUserFromMemberList(memberList, user.id, (error, newMemberList) => {
if (error) return callback(error);
if (isManager) {
// If member list is empty
if (_.isEmpty(newMemberList)) {
listOfIdElementToBeTransferred.push(eachLibraryItem.id);
// Make user archive a manager of the resource
_updateRoles(ctx, eachLibraryItem, archiveUser, resourceType, (error_) => {
if (error_) return callback(error_);
_removeFromLibrary(ctx, user.id, eachLibraryItem, resourceType, (error_) => {
if (error_) return callback(error_);
done();
});
});
} else {
const hasAnotherManager = _.find(
newMemberList,
(member) => member.role === AuthzConstants.role.MANAGER
);
// If there is another manager on the resource, send a notification email
if (hasAnotherManager) {
// We will just notify the members that the user will remove his account
_addMemberToList(
listElementByMember,
eachLibraryItem,
newMemberList,
false,
(
error
/* ListElementByMember */
) => {
if (error) return callback(error);
_removeFromLibrary(ctx, user.id, eachLibraryItem, resourceType, (error_) => {
if (error_) return callback(error_);
done();
});
}
);
// Has no manager
} else {
listOfIdElementToBeTransferred.push(eachLibraryItem.id);
// Make user archive a manager of the resource
_updateRoles(ctx, eachLibraryItem, archiveUser, resourceType, (error_) => {
if (error_) return callback(error_);
// We will notify the members that the user will remove his account and make the user archive manager of the resource
_addMemberToList(
listElementByMember,
eachLibraryItem,
newMemberList,
true,
(
error
/* ListElementByMember */
) => {
if (error) return callback(error);
_removeFromLibrary(ctx, user.id, eachLibraryItem, resourceType, (error_) => {
if (error_) return callback(error_);
done();
});
}
);
});
}
}
} else {
// We will just notify the members that the user will remove his account
_addMemberToList(
listElementByMember,
eachLibraryItem,
newMemberList,
false,
(error /* ListElementByMember */) => {
if (error) return callback(error);
_removeFromLibrary(ctx, user.id, eachLibraryItem, resourceType, (error_) => {
if (error_) return callback(error_);
done();
});
}
);
}
});
});
});
},
() => callback(null, listElementByMember, listOfIdElementToBeTransferred)
);
});
};
/**
* Clear the list by removing the deleted user
*
* @param {Array} memberList Array of user
* @param {String} userId User to delete from the array
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @param {Object} callback.newMemberList Array of user without the deleted user
* @api private
*/
const _removeUserFromMemberList = (memberList, userId, callback) =>
callback(
null,
_.reject(memberList, (element) => element.profile.id === userId)
);
/**
* Add elements to archive
*
* @param {String} cloneUserId The user archive of the tenant
* @param {String} principalToEliminate The user to delete
* @param {String} elementId The id element to delete
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @api private
*/
const _addToArchive = (cloneUserId, principalToEliminate, elementId, callback) => {
if (!cloneUserId || !principalToEliminate.id) {
return callback({ code: 400, msg: 'A user archive and a user to delete are required' });
}
if (!elementId) {
return callback();
}
// Return list of ids after removing duplicate elements
const duplicationRemoved = elementId.filter((element, index, self) => index === self.indexOf(element));
const monthsUntilDeletion = PrincipalsConfig.getValue(principalToEliminate.tenant.alias, 'user', DELETE);
const deletionDate = addMonths(new Date(), Number.parseInt(monthsUntilDeletion, 10));
// Add the element to data archive
PrincipalsDAO.addDataToArchive(
cloneUserId,
principalToEliminate.id,
duplicationRemoved,
deletionDate.toString(),
(error) => {
if (error) return callback(error);
return callback();
}
);
};
/**
* Check if the current user is manager a content
*
* @param {String} userId The user id
* @param {Object} resource The resource
* @param {String} resourceType Type can be : 'content', 'folder', 'discussion', 'meeting' or 'group'
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @param {Boolean} callback.hasRole True if the user are manager of the content
* @api private
*/
const _isManagerOfContent = (userId, resource, resourceType, callback) => {
const elementId = resourceType === FOLDER ? resource.groupId : resource.id;
AuthzAPI.hasRole(userId, elementId, AuthzConstants.role.MANAGER, (error, hasRole) => {
if (error) return callback(error);
return callback(null, hasRole);
});
};
/**
* Remove elements from library
*
* @param {Context} ctx Current execution context
* @param {String} userId The user id
* @param {String} element Element to remove from library
* @param {String} type Type can be : 'content', 'folder', 'discussion', 'meeting' or 'group'
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @api private
*/
const _removeFromLibrary = (ctx, userId, element, type, callback) => {
if (!element) return callback();
const changes = {};
changes[userId] = false;
switch (type) {
case CONTENT:
ContentAPI.removeContentFromLibrary(ctx, userId, element.id, (error) => {
if (error) return callback(error);
return callback();
});
break;
case FOLDER:
FolderAPI.removeFolderFromLibrary(ctx, userId, element.id, (error) => {
if (error) return callback(error);
return callback();
});
break;
case DISCUSSION:
DiscussionAPI.removeDiscussionFromLibrary(ctx, userId, element.id, (error) => {
if (error) return callback(error);
return callback();
});
break;
case MEETING:
MeetingsAPI.Meetings.removeMeetingFromLibrary(ctx, userId, element.id, (error) => {
if (error) return callback(error);
return callback();
});
break;
case GROUP:
AuthzAPI.updateRoles(element.id, changes, (error) => {
if (error) return callback(error);
return callback();
});
break;
default:
break;
}
};
/**
* Get members of a resource
*
* @param {Context} ctx Current execution context
* @param {String} elementId The element id
* @param {String} type Type can be : 'content', 'folder', 'discussion', 'meeting' or 'group'
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @param {Object} callback.memberList List of members
* @api private
*/
const _getMembers = (ctx, elementId, type, callback) => {
switch (type) {
case CONTENT:
ContentAPI.getContentMembersLibrary(ctx, elementId, null, null, (error, memberList) => {
if (error) return callback(error);
return callback(null, memberList);
});
break;
case FOLDER:
FolderAPI.getFolderMembers(ctx, elementId, null, null, (error, memberList) => {
if (error) return callback(error);
return callback(null, memberList);
});
break;
case DISCUSSION:
DiscussionAPI.getDiscussionMembers(ctx, elementId, null, null, (error, memberList) => {
if (error) return callback(error);
return callback(null, memberList);
});
break;
case MEETING:
MeetingsAPI.Meetings.getMeetingMembers(ctx, elementId, null, null, (error, memberList) => {
if (error) return callback(error);
return callback(null, memberList);
});
break;
case GROUP:
GroupAPI.getMembersLibrary(ctx, elementId, null, null, (error, memberList) => {
if (error) return callback(error);
return callback(null, memberList);
});
break;
default:
break;
}
};
/**
* Get members of a resource
*
* @param {Context} ctx Current execution context
* @param {String} userId The user id
* @param {String} type Type can be : 'content', 'folder', 'discussion', 'meeting' or 'group'
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @param {Object} callback.resourceList List of resources
* @api private
*/
const _getLibrary = (ctx, userId, type, callback) => {
switch (type) {
case CONTENT:
ContentAPI.getContentLibraryItems(ctx, userId, null, null, (error, contents) => {
if (error) return callback(error);
return callback(null, contents);
});
break;
case FOLDER:
FolderAPI.getFoldersLibrary(ctx, userId, null, null, (error, folders) => {
if (error) return callback(error);
return callback(null, folders);
});
break;
case DISCUSSION:
DiscussionAPI.getDiscussionsLibrary(ctx, userId, null, null, (error, discussions) => {
if (error) return callback(error);
return callback(null, discussions);
});
break;
case MEETING:
MeetingsAPI.Meetings.getMeetingsLibrary(ctx, userId, null, null, (error, meetings) => {
if (error) return callback(error);
return callback(null, meetings);
});
break;
case GROUP:
GroupAPI.getMembershipsLibrary(ctx, userId, null, null, (error, groups) => {
if (error) return callback(error);
return callback(null, groups);
});
break;
default:
break;
}
};
/**
* Update roles and make the archive user manager of the resource
*
* @param {Context} ctx Current execution context
* @param {Object} element The resource
* @param {String} archiveUser User Archive
* @param {String} type Type can be : 'content', 'folder', 'discussion', 'meeting' or 'group'
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @api private
*/
const _updateRoles = (ctx, element, archiveUser, type, callback) => {
const update = {};
update[archiveUser.archiveId] = AuthzConstants.role.MANAGER;
if (type === FOLDER) {
AuthzAPI.updateRoles(element.groupId, update, (error /* update */) => {
if (error) return callback(error);
return callback();
});
} else if (type === CONTENT) {
ContentAPI.setContentPermissions(ctx, element.id, update, (error /* update */) => {
if (error) return callback(error);
return callback();
});
} else {
AuthzAPI.updateRoles(element.id, update, (error /* update */) => {
if (error) return callback(error);
return callback();
});
}
};
/** =============================================================== **/
/** ===================== Definitive deletion ===================== **/
/** =============================================================== **/
/**
* Definitive delete of user
*
* @param {Context} ctx Current execution context
* @param {String} user User that will be deleted
* @param {String} alias Tenant alias
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
*/
const eliminateUser = (ctx, user, alias, callback) => {
callback = callback || function () {};
// Get userArchive
PrincipalsDAO.getArchivedUser(alias, (error, userArchive) => {
if (error) return callback(error);
// Get all data from user archive where data belonged to the user removed
PrincipalsDAO.getDataFromArchive(userArchive.archiveId, user.id, (error, data) => {
if (error) {
log().info({ userId: user.id, name: 'oae-principals' }, 'Unable to get data to eliminate, aborting.');
return callback(error);
}
async.series(
{
deleteResources(done) {
_deleteResourcePermissions(ctx, user, userArchive.archiveId, data, (error_) => {
if (error_) {
log().info(
{ userId: user.id, name: 'oae-principals', archiveId: userArchive.archiveId },
'Unable to delete resource permissions, skipping this step.'
);
}
done();
});
},
removeProfile(done) {
removeProfilePicture(ctx, user, (error_) => {
if (error_) {
log().info(
{ userId: user.id, name: 'oae-principals' },
'Unable to delete profile picture, skipping this step.'
);
}
done();
});
},
deleteFollowers(done) {
FollowingAPI.deleteFollowers(ctx, user, (error_) => {
if (error_) {
log().info(
{ userId: user.id, name: 'oae-principals' },
'Unable to delete user followers, skipping this step.'
);
}
done();
});
},
deleteFollowing(done) {
FollowingAPI.deleteFollowing(ctx, user, (error_) => {
if (error_) {
log().info(
{ userId: user.id, name: 'oae-principals' },
'Unable to delete user following, skipping this step.'
);
}
done();
});
},
deleteInvitations(done) {
AuthzInvitationDAO.deleteInvitationsByEmail(user.email, (error_) => {
if (error_) {
log().info(
{ userId: user.id, name: 'oae-principals' },
'Unable to delete invitations, skipping this step.'
);
}
done();
});
},
deleteActivity(done) {
ActivityAPI.removeActivityStream(ctx, user.id, (error_) => {
if (error_) {
log().info(
{ userId: user.id, name: 'oae-principals' },
'Unable to delete user activity streams, skipping this step.'
);
}
done();
});
},
removeFromCronTable(done) {
PrincipalsDAO.removePrincipalFromDataArchive(userArchive.archiveId, user.id, (error_) => {
if (error_) {
log().info(
{ userId: user.id, name: 'oae-principals', archiveId: userArchive.archiveId },
'Unable to remove principal from data archive, skipping this step.'
);
return callback(error_);
}
done();
});
},
isDeleted(done) {
// Determine if the principal was already deleted in the authz index before we set them and flip the principals deleted flag
AuthzDelete.isDeleted([user.id], (error /* wasDeleted */) => {
if (error) {
log().info(
{ userId: user.id, name: 'oae-principals' },
'Unable to remove principal from authz index, skipping this step.'
);
}
done();
});
}
},
(error_) => {
if (error_) {
log().info(
{ userId: user.id, name: 'oae-principals' },
'Found some errors while deleting data associated to user, moving on...'
);
}
// Get the login to delete the user form the table AuthenticationLoginId
AuthenticationAPI.getUserLoginIds(ctx, user.id, (error, login) => {
if (error) return callback(error);
// Set (or re-Set) the principal as deleted in the authz index
AuthzDelete.setDeleted(user.id, (error_) => {
if (error_) {
log().info('Unable to delete user from Authz index.');
return callback(error_);
}
// Delete a user from the database
PrincipalsDAO.fullyDeletePrincipal(user, login, (error_) => {
if (error_) return callback(error_);
// Notify that a user has been deleted
PrincipalsEmitter.emit(PrincipalsConstants.events.DELETED_USER, ctx, user, (error_) => {
if (error_) return callback(error_);
// Notify consumers that a user has been deleted
log().info(
{ userId: user.id, name: 'oae-principals' },
'Definitive deletion user with a mapped login id'
);
return callback(null, true);
});
});
});
});
}
);
});
});
};
/**
* Remove profile picture from file system
*
* @param {String} user The user to delete
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
*/
const removeProfilePicture = (ctx, user, callback) => {
PrincipalsDAO.getPrincipal(user.id, (error, principal) => {
if (error) return callback(error);
if (_.isEmpty(principal.picture)) return callback();
const pathSmallPicture = principal.picture.smallUri.split(':');
const pathMediumPicture = principal.picture.mediumUri.split(':');
const pathLargePicture = principal.picture.largeUri.split(':');
const pathStorageBackend = ContentUtil.getStorageBackend(ctx, principal.picture.largeUri).getRootDirectory();
fs.unlink(pathStorageBackend + '/' + pathSmallPicture[1], (error_) => {
if (error_) return callback(error_);
fs.unlink(pathStorageBackend + '/' + pathMediumPicture[1], (error_) => {
if (error_) return callback(error_);
fs.unlink(pathStorageBackend + '/' + pathLargePicture[1], (error_) => {
if (error_) return callback(error_);
return callback();
});
});
});
});
};
/**
* Remove all his rights on resources
*
* @param {Context} ctx Current execution context
* @param {String} user The user to delete
* @param {String} archiveId The user archive of the tenant
* @param {Object} data Data of the user from the table DataArchive
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @api private
*/
const _deleteResourcePermissions = (ctx, user, archiveId, data, callback) => {
// If there is no ids, return callback
if (!data.resourceId) {
return callback();
}
const ids = data.resourceId.split(',');
async.eachSeries(
ids,
(id, done) => {
// If the data belonged to the removed user, delete the resource
if (data.principalId === user.id) {
// Get type of resource
let idResource = id;
const splitId = idResource.split(':');
const resourceType = splitId[0];
// If it's a folder get the idGroup
_ifFolderGetIdGroup(ctx, idResource, splitId[0], (error, idFolder) => {
if (error) return callback(error);
if (idFolder) idResource = idFolder;
// Get role
AuthzAPI.getAllAuthzMembers(idResource, (error, memberIdRoles) => {
if (error) return callback(error);
const doesResourceHaveManagers =
_.chain(memberIdRoles)
.reject((each) => each.id === archiveId)
.pluck('role')
.filter((role) => role === AuthzConstants.role.MANAGER)
.value().length > 0;
// Lets erase only if there are no new managers in the meantime
const shouldProceed = !doesResourceHaveManagers;
// Remove the resource with the appropriate method
switch (resourceType) {
case CONTENT_PREFIX:
_deletePermissionsOnContent(ctx, shouldProceed, archiveId, idResource, (error_) => {
if (error_) return callback(error_);
});
break;
case DISCUSSION_PREFIX:
_deletePermissionsOnDiscussion(ctx, shouldProceed, archiveId, idResource, (error_) => {
if (error_) return callback(error_);
});
break;
case FOLDER_PREFIX:
_deletePermissionsOnFolder(ctx, shouldProceed, archiveId, id, (error_) => {
if (error_) return callback(error_);
});
break;
case GROUP_PREFIX:
_deletePermissionsOnGroup(ctx, shouldProceed, archiveId, idResource, (error_) => {
if (error_) return callback(error_);
});
break;
case MEETING_PREFIX:
_deletePermissionsOnMeeting(ctx, shouldProceed, archiveId, idResource, (error_) => {
if (error_) return callback(error_);
});
break;
default:
break;
}
return done();
});
});
}
},
() => callback()
);
};
/**
* Return the group a folder belongs to
*
* @param {Context} ctx Current execution context
* @param {String} idResource The id of the resource to delete
* @param {String} splitId The first part of a resource id (e.g. f:test:ryfQL_D4b, it will be 'f') who define the type of resource
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @api private
*/
const _ifFolderGetIdGroup = (ctx, idResource, splitId, callback) => {
if (splitId === FOLDER_PREFIX) {
FolderAPI.getFolder(ctx, idResource, (error, folder) => {
if (error) return callback(error);
return callback(null, folder.groupId);
});
} else {
return callback();
}
};
/**
* Remove right on content or remove it if there is no manager
*
* @param {Context} ctx Current execution context
* @param {Boolean} del Boolean who determine if the element should be remove or just removed from the library
* @param {String} archiveId The id user archive of the tenant
* @param {String} idResource The id of the resource to delete
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @api private
*/
const _deletePermissionsOnContent = (ctx, del, archiveId, idResource, callback) => {
if (del) {
ContentAPI.deleteContent(ctx, idResource, (error) => {
if (error) return callback(error);
return callback();
});
} else {
// If there is another manager on the content, remove it from the library
ContentAPI.removeContentFromLibrary(ctx, archiveId, idResource, (error) => {
if (error) return callback(error);
return callback();
});
}
};
/**
* Remove right on discussion or remove it if there is no manager
*
* @param {Context} ctx Current execution context
* @param {Boolean} del Boolean who determine if the element should be remove or just removed from the library
* @param {String} archiveId The id user archive of the tenant
* @param {String} idResource The id of the resource to delete
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @api private
*/
const _deletePermissionsOnDiscussion = (ctx, del, archiveId, idResource, callback) => {
if (del) {
// Remove the actual discussion profile
DiscussionAPI.deleteDiscussion(ctx, idResource, (error) => {
if (error) return callback(error);
return callback();
});
} else {
// If there is another manager on the discussion, remove it from the library
DiscussionAPI.removeDiscussionFromLibrary(ctx, archiveId, idResource, (error) => {
if (error) return callback(error);
return callback();
});
}
};
/**
* Remove right on folder or remove it if there is no manager
*
* @param {Context} ctx Current execution context
* @param {Boolean} del Boolean who determine if the element should be remove or just removed from the library
* @param {String} archiveId The id user archive of the tenant
* @param {String} idResource The id of the resource to delete
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @api private
*/
const _deletePermissionsOnFolder = (ctx, del, archiveId, idResource, callback) => {
if (del) {
FolderAPI.deleteFolder(ctx, idResource, false, (error /* content */) => {
if (error) return callback(error);
return callback();
});
} else {
// If there is another manager on the folder, remove it from the library
FolderAPI.removeFolderFromLibrary(ctx, archiveId, idResource, (error) => {
if (error) return callback(error);
return callback();
});
}
};
/**
* Remove right on group or remove it if there is no manager
*
* @param {Context} ctx Current execution context
* @param {Boolean} del Boolean who determine if the element should be remove or just removed from the library
* @param {String} archiveId The id user archive of the tenant
* @param {String} idResource The id of the resource to delete
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @api private
*/
const _deletePermissionsOnGroup = (ctx, del, archiveId, idResource, callback) => {
if (del) {
// Remove group
GroupAPI.deleteGroup(ctx, idResource, (error) => {
if (error) return callback(error);
// Remove roles
const update = {};
update[archiveId] = false;
AuthzAPI.updateRoles(idResource, update, (error /* usersToInvalidate */) => {
if (error) return callback(error);
return callback();
});
});
} else {
// If there is another manager on the group, remove it from the library
GroupAPI.leaveGroup(ctx, idResource, (error) => {
if (error) return callback(error);
return callback();
});
}
};
/**
* Remove right on meeting or remove it if there is no manager
*
* @param {Context} ctx Current execution context
* @param {Boolean} del Boolean who determine if the element should be remove or just removed from the library
* @param {String} archiveId The id user archive of the tenant
* @param {String} idResource The id of the resource to delete
* @param {Function} callback Standard callback function
* @param {Object} callback.err An error that occured, if any
* @api private
*/
const _deletePermissionsOnMeeting = (ctx, del, archiveId, idResource, callback) => {
if (del) {
// Remove meeting
MeetingsAPI.Meetings.deleteMeeting(ctx, idResource, (error) => {
if (error) return callback(error);
return callback();
});
} else {
// If there is another manager on the meeting, remove it from the library
MeetingsAPI.Meetings.removeMeetingFromLibrary(ctx, archiveId, idResource, (error) => {
if (error) return callback(error);
return callback();
});
}
};
export {
transferPermissionsToCloneUser as transferUsersDataToCloneUser,
removeProfilePicture,
eliminateUser,
fetchOrCloneFromUser
};