Skip to content

Commit

Permalink
Merge pull request #662 from Esri/f/shared-edit-group-api-update
Browse files Browse the repository at this point in the history
feat(portal): add reassignItem
  • Loading branch information
dbouwman committed Feb 27, 2020
2 parents f3f856b + 5afe3be commit 2fe62ae
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/arcgis-rest-portal/src/index.ts
Expand Up @@ -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";
Expand Down
61 changes: 61 additions & 0 deletions 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<IReassignItemResponse> {
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);
});
}
9 changes: 4 additions & 5 deletions packages/arcgis-rest-portal/src/sharing/group-sharing.ts
Expand Up @@ -104,20 +104,19 @@ 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) {
throw Error(
`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.`
Expand Down
5 changes: 3 additions & 2 deletions packages/arcgis-rest-portal/src/sharing/helpers.ts
Expand Up @@ -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";

Expand Down Expand Up @@ -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<boolean> {
export function isOrgAdmin(
requestOptions: IUserRequestOptions
): Promise<boolean> {
const session = requestOptions.authentication;

return session.getUser(requestOptions).then((user: IUser) => {
Expand Down
123 changes: 123 additions & 0 deletions 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);
});
});
});

0 comments on commit 2fe62ae

Please sign in to comment.