diff --git a/common/ASC.Core.Common/Security/EmailValidationKeyProvider.cs b/common/ASC.Core.Common/Security/EmailValidationKeyProvider.cs index 26052ee9ff..228fe2a186 100644 --- a/common/ASC.Core.Common/Security/EmailValidationKeyProvider.cs +++ b/common/ASC.Core.Common/Security/EmailValidationKeyProvider.cs @@ -173,6 +173,17 @@ namespace ASC.Security.Cryptography checkKeyResult = ValidateEmailKey(Email + Type + (string.IsNullOrEmpty(hash) ? string.Empty : Hasher.Base64Hash(hash)) + UiD, Key, ValidInterval); break; case ConfirmType.Activation: + checkKeyResult = ValidateEmailKey(Email + Type + UiD, Key, ValidInterval); + break; + case ConfirmType.ProfileRemove: + // validate UiD + if (P == 1) + { + var user = CoreContext.UserManager.GetUsers(UiD.GetValueOrDefault()); + if (user == null || user.Status == EmployeeStatus.Terminated || SecurityContext.IsAuthenticated && SecurityContext.CurrentAccount.ID != UiD) + return ValidationResult.Invalid; + } + checkKeyResult = ValidateEmailKey(Email + Type + UiD, Key, ValidInterval); break; default: diff --git a/products/ASC.People/Client/src/components/pages/GroupAction/Section/Body/index.js b/products/ASC.People/Client/src/components/pages/GroupAction/Section/Body/index.js index 61c5214e49..e92fcdf819 100644 --- a/products/ASC.People/Client/src/components/pages/GroupAction/Section/Body/index.js +++ b/products/ASC.People/Client/src/components/pages/GroupAction/Section/Body/index.js @@ -28,7 +28,7 @@ import { updateGroup } from "../../../../../store/group/actions"; import styled from "styled-components"; -import { fetchSelectorUsers } from "../../../../../store/people/actions"; +import { fetchSelectorUsers, fetchPeople, fetchGroups } from "../../../../../store/people/actions"; import { GUID_EMPTY } from "../../../../../helpers/constants"; import isEqual from "lodash/isEqual"; @@ -110,34 +110,40 @@ class SectionBodyContent extends React.Component { key: 0, label: t("CustomAddEmployee", { typeUser }) }, - groupMembers: group && group.members ? group.members.map(m => { - return { - key: m.id, - label: m.displayName - } - }) : [], - groupManager: group && group.manager ? { - key: group.manager.id, - label: group.manager.displayName - } : { - key: GUID_EMPTY, - label: t("CustomAddEmployee", { typeUser }) - } + groupMembers: + group && group.members + ? group.members.map(m => { + return { + key: m.id, + label: m.displayName + }; + }) + : [], + groupManager: + group && group.manager + ? { + key: group.manager.id, + label: group.manager.displayName + } + : { + key: GUID_EMPTY, + label: t("CustomAddEmployee", { typeUser }) + } }; return newState; - } + }; componentDidMount() { const { users, fetchSelectorUsers } = this.props; - if(!users || !users.length) { + if (!users || !users.length) { fetchSelectorUsers(); } } componentDidUpdate(prevProps) { //const { users, group } = this.props; - if(!isEqual(this.props, prevProps)) { + if (!isEqual(this.props, prevProps)) { this.setState(this.mapPropsToState()); } } @@ -163,12 +169,12 @@ class SectionBodyContent extends React.Component { }; onHeadSelectorSelect = option => { - this.setState({ + this.setState({ groupManager: { key: option.key, label: option.label }, - isHeaderSelectorOpen: !this.state.isHeaderSelectorOpen + isHeaderSelectorOpen: !this.state.isHeaderSelectorOpen }); }; @@ -178,24 +184,24 @@ class SectionBodyContent extends React.Component { }); }; - onUsersSelectorSearch = (value) => { + onUsersSelectorSearch = value => { /*setOptions( options.filter(option => { return option.label.indexOf(value) > -1; }) );*/ }; - onUsersSelectorSelect = (selectedOptions) => { + onUsersSelectorSelect = selectedOptions => { //console.log("onSelect", selectedOptions); //this.onUsersSelectorClick(); - this.setState({ + this.setState({ groupMembers: selectedOptions.map(option => { return { key: option.key, label: option.label }; }), - isUsersSelectorOpen: !this.state.isUsersSelectorOpen + isUsersSelectorOpen: !this.state.isUsersSelectorOpen }); }; @@ -211,28 +217,33 @@ class SectionBodyContent extends React.Component { }); }; + save = (group) => { + const { createGroup, updateGroup } = this.props; + return group.id + ? updateGroup(group.id, group.name, group.managerKey, group.members) + : createGroup(group.name, group.managerKey, group.members); + }; + onSave = () => { - const { history, group, createGroup, updateGroup, resetGroup } = this.props; + const { group } = this.props; const { groupName, groupManager, groupMembers } = this.state; if (!groupName || !groupName.trim().length) return false; this.setState({ inLoading: true }); - (group && group.id - ? updateGroup( - group.id, - groupName, - groupManager.key, - groupMembers.map(u => u.key) - ) - : createGroup(groupName, groupManager.key, groupMembers.map(u => u.key)) - ) - .then(() => { - toastr.success("Success"); - //this.setState({ inLoading: true }); - history.goBack(); - resetGroup(); + const newGroup = { + name: groupName, + managerKey: groupManager.key, + members: groupMembers.map(u => u.key) + }; + + if(group && group.id) + newGroup.id = group.id; + + this.save(newGroup) + .then(group => { + toastr.success(`Group '${group.name}' has been saved successfully`); }) .catch(error => { toastr.error(error.message); @@ -247,11 +258,11 @@ class SectionBodyContent extends React.Component { history.goBack(); }; - onSelectedItemClose = (member) => { - this.setState({ + onSelectedItemClose = member => { + this.setState({ groupMembers: this.state.groupMembers.filter(g => g.key !== member.key) }); - } + }; renderModal = () => { const { groups, modalVisible } = this.state; @@ -559,11 +570,11 @@ function mapStateToProps(state) { settings: state.auth.settings, group: state.group.targetGroup, groups: convertGroups(state.people.groups), - users: convertUsers(state.people.selector.users) //TODO: replace to api requests with search + users: convertUsers(state.people.selector.users), //TODO: replace to api requests with search }; } export default connect( mapStateToProps, - { resetGroup, createGroup, updateGroup, fetchSelectorUsers } + { resetGroup, createGroup, updateGroup, fetchSelectorUsers, fetchPeople, fetchGroups } )(withRouter(withTranslation()(SectionBodyContent))); diff --git a/products/ASC.People/Client/src/helpers/utils.js b/products/ASC.People/Client/src/helpers/utils.js new file mode 100644 index 0000000000..ae8d90a910 --- /dev/null +++ b/products/ASC.People/Client/src/helpers/utils.js @@ -0,0 +1,6 @@ +export function checkResponseError(res) { + if (res && res.data && res.data.error) { + console.error(res.data.error); + throw new Error(res.data.error.message); + } +} \ No newline at end of file diff --git a/products/ASC.People/Client/src/store/auth/actions.js b/products/ASC.People/Client/src/store/auth/actions.js index b7eed49761..70b8f962e5 100644 --- a/products/ASC.People/Client/src/store/auth/actions.js +++ b/products/ASC.People/Client/src/store/auth/actions.js @@ -1,5 +1,5 @@ import * as api from "../services/api"; -import { setGroups, fetchPeopleAsync } from "../people/actions"; +import { fetchGroups, fetchPeople } from "../people/actions"; import setAuthorizationToken from "../../store/services/setAuthorizationToken"; import { getFilterByLocation } from "../../helpers/converters"; import config from "../../../package.json"; @@ -57,9 +57,7 @@ export async function getUserInfo(dispatch) { dispatch(setModules(modules)); dispatch(setSettings(newSettings)); - const groupResp = await api.getGroupList(); - - dispatch(setGroups(groupResp.data.response)); + await fetchGroups(dispatch); var re = new RegExp(`${config.homepage}((/?)$|/filter)`, "gm"); const match = window.location.pathname.match(re); @@ -67,7 +65,7 @@ export async function getUserInfo(dispatch) { if (match && match.length > 0) { const newFilter = getFilterByLocation(window.location); - await fetchPeopleAsync(dispatch, newFilter); + await fetchPeople(newFilter, dispatch); } return dispatch(setIsLoaded(true)); diff --git a/products/ASC.People/Client/src/store/group/actions.js b/products/ASC.People/Client/src/store/group/actions.js index 470c2cfb2d..7ba2fb6878 100644 --- a/products/ASC.People/Client/src/store/group/actions.js +++ b/products/ASC.People/Client/src/store/group/actions.js @@ -1,5 +1,7 @@ import * as api from "../../store/services/api"; -import { setGroups, fetchPeopleByFilter } from "../people/actions"; +import { setGroups, fetchPeople, fetchGroups } from "../people/actions"; +import { checkResponseError } from "../../helpers/utils"; +import history from "../../history"; export const SET_GROUP = "SET_GROUP"; export const CLEAN_GROUP = "CLEAN_GROUP"; @@ -17,13 +19,6 @@ export function resetGroup() { }; } -export function checkResponseError(res) { - if (res && res.data && res.data.error) { - console.error(res.data.error); - throw new Error(res.data.error.message); - } -} - export function fetchGroup(groupId) { return dispatch => { api.getGroup(groupId).then(res => { @@ -36,23 +31,16 @@ export function fetchGroup(groupId) { export function createGroup(groupName, groupManager, members) { return (dispatch, getState) => { const { people } = getState(); - const { groups, filter } = people; - - let newGroup; + const { groups } = people; return api .createGroup(groupName, groupManager, members) .then(res => { checkResponseError(res); - newGroup = res.data.response; - - //dispatch(setGroup(newGroup)); - return dispatch(setGroups([...groups, newGroup])); - }) - .then(() => { - return fetchPeopleByFilter(dispatch, filter); - }) - .then(() => { + history.goBack(); + const newGroup = res.data.response; + dispatch(resetGroup()); + dispatch(setGroups([...groups, newGroup])); return Promise.resolve(newGroup); }); }; @@ -61,28 +49,19 @@ export function createGroup(groupName, groupManager, members) { export function updateGroup(id, groupName, groupManager, members) { return (dispatch, getState) => { const { people } = getState(); - const { groups, filter } = people; - - let newGroup; + const { groups } = people; return api .updateGroup(id, groupName, groupManager, members) .then(res => { checkResponseError(res); - newGroup = res.data.response; - - //dispatch(setGroup(newGroup)); - + history.goBack(); + const newGroup = res.data.response; + dispatch(resetGroup()); const newGroups = groups.map(g => g.id === newGroup.id ? newGroup : g ); - - return dispatch(setGroups(newGroups)); - }) - .then(() => { - return fetchPeopleByFilter(dispatch, filter); - }) - .then(() => { + dispatch(setGroups(newGroups)); return Promise.resolve(newGroup); }); }; @@ -101,7 +80,7 @@ export function deleteGroup(id) { }) .then(() => { const newFilter = filter.clone(true); - return fetchPeopleByFilter(dispatch, newFilter); + return fetchPeople(newFilter, dispatch); }); }; } diff --git a/products/ASC.People/Client/src/store/people/actions.js b/products/ASC.People/Client/src/store/people/actions.js index 36a42c7da8..3b096eafba 100644 --- a/products/ASC.People/Client/src/store/people/actions.js +++ b/products/ASC.People/Client/src/store/people/actions.js @@ -14,6 +14,7 @@ import { PAGE_COUNT, EmployeeStatus } from "../../helpers/constants"; +import { checkResponseError } from "../../helpers/utils"; export const SET_GROUPS = "SET_GROUPS"; export const SET_USERS = "SET_USERS"; @@ -69,7 +70,7 @@ export function selectGroup(groupId) { let newFilter = filter.clone(); newFilter.group = groupId; - return fetchPeopleByFilter(dispatch, newFilter); + return fetchPeople(newFilter, dispatch); }; } @@ -147,14 +148,38 @@ export function fetchSelectorUsers() { }; } -export function fetchPeople(filter) { - return dispatch => { - return fetchPeopleByFilter(dispatch, filter); +export function fetchGroups(dispatchFunc = null) { + return api.getGroupList() + .then(res => { + checkResponseError(res); + return dispatchFunc + ? dispatchFunc(setGroups(res.data.response)) + : Promise.resolve(dispatch => dispatch(setGroups(res.data.response))); + }); +} + + +export function fetchPeople(filter, dispatchFunc = null) { + return dispatchFunc ? fetchPeopleByFilter(dispatchFunc, filter) + : (dispatch, getState) => { + if(filter) { + return fetchPeopleByFilter(dispatch, filter); + } + else { + const {people} = getState(); + const {filter} = people; + return fetchPeopleByFilter(dispatch, filter); + } }; } -export function fetchPeopleByFilter(dispatch, filter) { - let filterData = (filter && filter.clone()) || Filter.getDefault(); +function fetchPeopleByFilter(dispatch, filter) { + let filterData = (filter && filter.clone()); + + if(!filterData) { + filterData = Filter.getDefault(); + filterData.employeeStatus = EmployeeStatus.Active; + } return api.getUserList(filterData).then(res => { filterData.total = res.data.total; @@ -167,7 +192,7 @@ export function fetchPeopleByFilter(dispatch, filter) { }); } -export async function fetchPeopleAsync(dispatch, filter = null) { +/*export async function fetchPeopleAsync(dispatch, filter = null) { let filterData = (filter && filter.clone()); if(!filterData) { @@ -185,7 +210,7 @@ export async function fetchPeopleAsync(dispatch, filter = null) { groupId: filterData.group }); dispatch(setUsers(usersResp.data.response)); -} +}*/ export function updateUserStatus(status, userIds) { return dispatch => { @@ -224,6 +249,6 @@ export function resetFilter() { const newFilter = filter.clone(true); - return fetchPeopleByFilter(dispatch, newFilter); + return fetchPeople(newFilter, dispatch); }; } diff --git a/products/ASC.People/Client/src/store/profile/actions.js b/products/ASC.People/Client/src/store/profile/actions.js index df027e5c01..3ee2ab4940 100644 --- a/products/ASC.People/Client/src/store/profile/actions.js +++ b/products/ASC.People/Client/src/store/profile/actions.js @@ -1,8 +1,9 @@ import * as api from "../../store/services/api"; import { isMe } from '../auth/selectors'; import { getUserByUserName } from '../people/selectors'; -import { fetchPeopleByFilter } from "../people/actions"; +import { fetchPeople } from "../people/actions"; import { setCurrentUser } from "../auth/actions"; +import { checkResponseError } from "../../helpers/utils"; export const SET_PROFILE = 'SET_PROFILE'; export const CLEAN_PROFILE = 'CLEAN_PROFILE'; @@ -20,12 +21,7 @@ export function resetProfile() { }; }; -export function checkResponseError(res) { - if (res && res.data && res.data.error) { - console.error(res.data.error); - throw new Error(res.data.error.message); - } -} + export function employeeWrapperToMemberModel(profile) { const comment = profile.notes; @@ -67,7 +63,7 @@ export function createProfile(profile) { result = res.data.response; return dispatch(setProfile(result)); }).then(() => { - return fetchPeopleByFilter(dispatch, filter); + return fetchPeople(filter, dispatch); }).then(() => { return Promise.resolve(result); }); @@ -86,7 +82,7 @@ export function updateProfile(profile) { result = res.data.response; return Promise.resolve(dispatch(setProfile(result))); }).then(() => { - return fetchPeopleByFilter(dispatch, filter); + return fetchPeople(filter, dispatch); }).then(() => { return Promise.resolve(result); }); diff --git a/products/ASC.People/Server/Controllers/PeopleController.cs b/products/ASC.People/Server/Controllers/PeopleController.cs index 7de1f2981a..acf80f1cfc 100644 --- a/products/ASC.People/Server/Controllers/PeopleController.cs +++ b/products/ASC.People/Server/Controllers/PeopleController.cs @@ -575,6 +575,50 @@ namespace ASC.Employee.Core.Controllers return new EmployeeWraperFull(user, ApiContext); } + [Delete("@self")] + [Authorize(AuthenticationSchemes = "confirm", Roles = "ProfileRemove")] + public EmployeeWraperFull DeleteProfile() + { + ApiContext.AuthByClaim(); + + if (CoreContext.UserManager.IsSystemUser(SecurityContext.CurrentAccount.ID)) + throw new SecurityException(); + + var user = GetUserInfo(SecurityContext.CurrentAccount.ID.ToString()); + + if (!CoreContext.UserManager.UserExists(user)) + throw new Exception(Resource.ErrorUserNotFound); + + if(user.IsLDAP()) + throw new SecurityException(); + + _ = SecurityContext.AuthenticateMe(ASC.Core.Configuration.Constants.CoreSystem); + + user.Status = EmployeeStatus.Terminated; + + CoreContext.UserManager.SaveUserInfo(Tenant, user); + + var userName = user.DisplayUserName(false); + MessageService.Send(MessageAction.UsersUpdatedStatus, MessageTarget.Create(user.ID), userName); + + HttpContext.ResetUserCookie(Tenant.TenantId, user.ID); + MessageService.Send(MessageAction.CookieSettingsUpdated); + + if (CoreContext.Configuration.Personal) + { + UserPhotoManager.RemovePhoto(Tenant, user.ID); + CoreContext.UserManager.DeleteUser(Tenant, user.ID); + MessageService.Send(MessageAction.UserDeleted, MessageTarget.Create(user.ID), userName); + } + else + { + //StudioNotifyService.Instance.SendMsgProfileHasDeletedItself(user); + //StudioNotifyService.SendMsgProfileDeletion(Tenant.TenantId, user); + } + + return new EmployeeWraperFull(user, ApiContext); + } + [Update("{userid}/contacts")] public EmployeeWraperFull UpdateMemberContacts(string userid, UpdateMemberModel memberModel) { diff --git a/web/ASC.Web.Client/src/components/pages/Confirm/sub-components/profileRemove.js b/web/ASC.Web.Client/src/components/pages/Confirm/sub-components/profileRemove.js index bb46ee9d9c..e9a2e43d94 100644 --- a/web/ASC.Web.Client/src/components/pages/Confirm/sub-components/profileRemove.js +++ b/web/ASC.Web.Client/src/components/pages/Confirm/sub-components/profileRemove.js @@ -5,8 +5,8 @@ import styled from 'styled-components'; import { welcomePageTitle } from './../../../../helpers/customNames'; import PropTypes from 'prop-types'; import { withTranslation } from 'react-i18next'; -import { deleteUser, updateUserStatus } from './../../../../store/services/api' -import { EmployeeStatus } from './../../../../helpers/constants'; +import { deleteSelf } from './../../../../store/services/api'; +import setAuthorizationToken from './../../../../store/services/setAuthorizationToken'; const ProfileRemoveContainer = styled.div` display: flex; @@ -40,17 +40,13 @@ class ProfileRemove extends React.PureComponent { onDeleteProfile = (e) => { this.setState({ isLoading: true }, function () { const { linkData } = this.props; - - updateUserStatus(EmployeeStatus.Disabled, [linkData.uid], linkData.confirmHeader) - .then((res) => { - console.log('success update status', res) - return deleteUser(linkData.uid); - }) + deleteSelf(linkData.confirmHeader) .then((res) => { this.setState({ isLoading: false, isProfileDeleted: true }); + setAuthorizationToken(); console.log('success delete', res) }) .catch((e) => { diff --git a/web/ASC.Web.Client/src/store/services/api.js b/web/ASC.Web.Client/src/store/services/api.js index 92cf265518..e437147c75 100644 --- a/web/ASC.Web.Client/src/store/services/api.js +++ b/web/ASC.Web.Client/src/store/services/api.js @@ -85,20 +85,13 @@ export function checkConfirmLink(data) { : axios.post(`${API_URL}/authentication/confirm.json`, data); } -export function deleteUser(userId) { +export function deleteSelf(key) { return IS_FAKE - ? fakeApi.deleteUser(userId) - : axios.delete(`${API_URL}/people/${userId}.json`); -} - -export function updateUserStatus(status, userIds, key) { - return IS_FAKE - ? fakeApi.updateUserStatus(status, userIds) - : axios.put(`${API_URL}/people/status/${status}`, { userIds }, { + ? fakeApi.deleteUser(key) + : axios.delete(`${API_URL}/people/@self`, { headers: { confirm: key } }); } - export function sendInstructionsToChangePassword(email) { return IS_FAKE ? fakeApi.sendInstructionsToChangePassword() diff --git a/web/ASC.Web.Core/Notify/StudioNotifyService.cs b/web/ASC.Web.Core/Notify/StudioNotifyService.cs index 2852c846da..c138f68383 100644 --- a/web/ASC.Web.Core/Notify/StudioNotifyService.cs +++ b/web/ASC.Web.Core/Notify/StudioNotifyService.cs @@ -447,7 +447,7 @@ namespace ASC.Web.Studio.Core.Notify public void SendMsgProfileDeletion(int tenantId, UserInfo user) { - var confirmationUrl = CommonLinkUtility.GetConfirmationUrl(tenantId, user.Email, ConfirmType.ProfileRemove, null, SecurityContext.CurrentAccount.ID); + var confirmationUrl = CommonLinkUtility.GetConfirmationUrl(tenantId, user.Email, ConfirmType.ProfileRemove, SecurityContext.CurrentAccount.ID, SecurityContext.CurrentAccount.ID); static string greenButtonText() => CoreContext.Configuration.Personal ? WebstudioNotifyPatternResource.ButtonConfirmTermination : WebstudioNotifyPatternResource.ButtonRemoveProfile;