Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(portal): add reassignItem #662

Merged
merged 2 commits into from
Feb 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/arcgis-rest-portal/src/index.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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);
});
});
});