diff --git a/packages/arcgis-rest-portal/src/index.ts b/packages/arcgis-rest-portal/src/index.ts index cb1970bd53..ba8b0ae081 100644 --- a/packages/arcgis-rest-portal/src/index.ts +++ b/packages/arcgis-rest-portal/src/index.ts @@ -5,6 +5,7 @@ export * from "./items/add"; export * from "./items/create"; export * from "./items/get"; export * from "./items/protect"; +export * from "./items/reassign"; export * from "./items/remove"; export * from "./items/search"; export * from "./items/update"; diff --git a/packages/arcgis-rest-portal/src/items/reassign.ts b/packages/arcgis-rest-portal/src/items/reassign.ts new file mode 100644 index 0000000000..837b5b4019 --- /dev/null +++ b/packages/arcgis-rest-portal/src/items/reassign.ts @@ -0,0 +1,61 @@ +/* Copyright (c) 2018 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ +import { request, IRequestOptions } from "@esri/arcgis-rest-request"; +import { IItem } from "@esri/arcgis-rest-types"; +import { getPortalUrl } from "../util/get-portal-url"; +import { isOrgAdmin } from "../sharing/helpers"; +import { IUserRequestOptions } from "@esri/arcgis-rest-auth"; + +interface IReassignItemOptions extends IUserRequestOptions { + id: string; + currentOwner: string; + targetUsername: string; + targetFolderName?: string; +} + +interface IReassignItemResponse { + success: boolean; + itemId: string; +} + +/** + * ```js + * import { reassignItem } from '@esri/arcgis-rest-portal'; + * // + * reassignItem({ + * id: "abc123", + * currentOwner: "charles", + * targetUsername: "leslie", + * authentication + * }) + * ``` + * Reassign an item from one user to another. + * Caller must be an org admin or the request will fail. + * `currentOwner` and `targetUsername` must be in the same + * organization or the request will fail + * @param reassignOptions - Options for the request + */ +export function reassignItem( + reassignOptions: IReassignItemOptions +): Promise { + return isOrgAdmin(reassignOptions).then(isAdmin => { + if (!isAdmin) { + throw Error( + `Item ${reassignOptions.id} can not be reassigned because current user is not an organization administrator.` + ); + } + // we're operating as an org-admin + const url = `${getPortalUrl(reassignOptions)}/content/users/${ + reassignOptions.currentOwner + }/items/${reassignOptions.id}/reassign`; + + const opts = { + params: { + targetUsername: reassignOptions.targetUsername, + targetFolderName: reassignOptions.targetFolderName + }, + authentication: reassignOptions.authentication + }; + return request(url, opts); + }); +} diff --git a/packages/arcgis-rest-portal/src/sharing/group-sharing.ts b/packages/arcgis-rest-portal/src/sharing/group-sharing.ts index 592a1979b9..405babb7be 100644 --- a/packages/arcgis-rest-portal/src/sharing/group-sharing.ts +++ b/packages/arcgis-rest-portal/src/sharing/group-sharing.ts @@ -104,8 +104,7 @@ function changeGroupSharing( ); } else { // ...they are some level of membership or org-admin - - // if the current user does not own the item, we had more checks... + // if the current user does not own the item... if (itemOwner !== username) { // only item owners can share/unshare items w/ shared editing groups if (isSharedEditingGroup) { @@ -113,11 +112,11 @@ function changeGroupSharing( `This item can not be ${requestOptions.action}d to shared editing group ${requestOptions.groupId} by ${username} as they not the item owner.` ); } - // only item-owners, group-admin's, group-owners can unshare an item from a normal group + // only item-owners, group-admin's, group-owners can unshare an item from a group if ( requestOptions.action === "unshare" && - membership !== "admin" && - membership !== "owner" + membership !== "admin" && // not group admin + membership !== "owner" // not group owner ) { throw Error( `This item can not be ${requestOptions.action}d from group ${requestOptions.groupId} by ${username} as they not the item owner, group admin or group owner.` diff --git a/packages/arcgis-rest-portal/src/sharing/helpers.ts b/packages/arcgis-rest-portal/src/sharing/helpers.ts index 30f767dab8..de8ee0f11f 100644 --- a/packages/arcgis-rest-portal/src/sharing/helpers.ts +++ b/packages/arcgis-rest-portal/src/sharing/helpers.ts @@ -3,7 +3,6 @@ import { IGroup, IUser, GroupMembership } from "@esri/arcgis-rest-types"; import { IUserRequestOptions } from "@esri/arcgis-rest-auth"; - import { getPortalUrl } from "../util/get-portal-url"; import { getGroup } from "../groups/get"; @@ -43,7 +42,9 @@ export function isItemOwner(requestOptions: ISharingOptions): boolean { * @param requestOptions * @returns Promise resolving in a boolean indicating if the user is an ArcGIS Organization administrator */ -export function isOrgAdmin(requestOptions: ISharingOptions): Promise { +export function isOrgAdmin( + requestOptions: IUserRequestOptions +): Promise { const session = requestOptions.authentication; return session.getUser(requestOptions).then((user: IUser) => { diff --git a/packages/arcgis-rest-portal/test/items/reassign.test.ts b/packages/arcgis-rest-portal/test/items/reassign.test.ts new file mode 100644 index 0000000000..f71444eb0d --- /dev/null +++ b/packages/arcgis-rest-portal/test/items/reassign.test.ts @@ -0,0 +1,123 @@ +/* Copyright (c) 2018 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import * as fetchMock from "fetch-mock"; + +import { reassignItem } from "../../src/items/reassign"; + +import { UserSession } from "@esri/arcgis-rest-auth"; +import { TOMORROW } from "@esri/arcgis-rest-auth/test/utils"; +import { + GroupMemberUserResponse, + OrgAdminUserResponse +} from "../mocks/users/user"; + +describe("reassignItem", () => { + afterEach(fetchMock.restore); + + const MOCK_USER_SESSION = new UserSession({ + clientId: "clientId", + redirectUri: "https://example-app.com/redirect-uri", + token: "fake-token", + tokenExpires: TOMORROW, + refreshToken: "refreshToken", + refreshTokenExpires: TOMORROW, + refreshTokenTTL: 1440, + username: "casey", + password: "123456", + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }); + + it("shoulds throw if not authd as org_admin", done => { + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + GroupMemberUserResponse + ); + reassignItem({ + id: "3ef", + currentOwner: "alex", + targetUsername: "blake", + authentication: MOCK_USER_SESSION + }).catch(e => { + expect(e.message).toBe( + "Item 3ef can not be reassigned because current user is not an organization administrator." + ); + done(); + }); + }); + + it("should send the folder if passed", done => { + fetchMock + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + OrgAdminUserResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/alex/items/3ef/reassign", + { success: true, itemId: "3ef" } + ); + reassignItem({ + id: "3ef", + currentOwner: "alex", + targetUsername: "blake", + targetFolderName: "folder1", + authentication: MOCK_USER_SESSION + }) + .then(resp => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + expect(resp.success).toBe(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/alex/items/3ef/reassign" + ); + expect(url).toBe( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/alex/items/3ef/reassign" + ); + expect(options.method).toBe("POST"); + expect(options.body).toContain("targetUsername=blake"); + expect(options.body).toContain("targetFolderName=folder1"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should not send the folder if not passed", done => { + fetchMock + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + OrgAdminUserResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/alex/items/3ef/reassign", + { success: true, itemId: "3ef" } + ); + reassignItem({ + id: "3ef", + currentOwner: "alex", + targetUsername: "blake", + authentication: MOCK_USER_SESSION + }) + .then(resp => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + expect(resp.success).toBe(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/alex/items/3ef/reassign" + ); + expect(url).toBe( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/alex/items/3ef/reassign" + ); + expect(options.method).toBe("POST"); + expect(options.body).toContain("targetUsername=blake"); + expect(options.body).not.toContain("targetFolderName"); + done(); + }) + .catch(e => { + fail(e); + }); + }); +});