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

Transfer Resource Ownership #2407

Merged
merged 9 commits into from Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 64 additions & 0 deletions common/vueapp/TransferResource.vue
@@ -0,0 +1,64 @@
<template>
<b-modal
id="transfer-modal"
title="Transfer ownership to another user">
<b-form>
<b-input-group
prepend="User ID">
<b-form-input
required
id="userId"
type="text"
v-model="userId"
:state="!userId ? false : true"
placeholder="Enter a single user's ID">
</b-form-input>
</b-input-group>
</b-form>
<template #modal-footer>
<div class="w-100 d-flex justify-content-between">
<b-button
title="Cancel"
variant="danger"
@click="cancel">
<span class="fa fa-times" />
Cancel
</b-button>
<b-button
variant="success"
v-b-tooltip.hover
:disabled="!userId"
@click="transferResource"
title="Transfer Ownership">
<span class="fa fa-share mr-1" />
Transfer
</b-button>
</div>
</template> <!-- /modal footer -->
</b-modal>
</template>

<script>
export default {
name: 'TransferResource',
data () {
return {
userId: ''
};
},
methods: {
cancel () {
this.$emit('transfer-resource');
this.$bvModal.hide('transfer-modal');
},
transferResource () {
if (!this.userId) {
return;
}

this.$emit('transfer-resource', { userId: this.userId });
this.$bvModal.hide('transfer-modal');
}
}
};
</script>
17 changes: 16 additions & 1 deletion tests/api-views.t
@@ -1,4 +1,4 @@
use Test::More tests => 35;
use Test::More tests => 41;
use MolochTest;
use JSON;
use Test::Differences;
Expand Down Expand Up @@ -116,6 +116,21 @@ eq_or_diff($info->{view}->{editRoles}, from_json('["cont3xtUser"]'), "added edit
$info = viewerPutToken("/api/view/${id4}?molochRegressionUser=test2", '{"name": "updated!", "expression": "ip == 4.3.2.1", "roles":["arkimeUser"], "users":"", "editRoles":["cont3xtUser"]}', $token2);
ok($info->{success}, "can update view with editRoles");

# test2 cannot transfer ownership (not admin or creator)
$info = viewerPutToken("/api/view/${id4}?molochRegressionUser=test2", '{"name": "view4", "expression": "ip == 4.3.2.1", "roles":["arkimeUser"], "users":"", "editRoles":["cont3xtUser"], "user":"asdf"}', $token2);
ok(!$info->{success}, "cannot transfer ownership without being admin or creator");
eq_or_diff($info->{text}, "Permission denied");

# can't transfer ownership to invalid user
$info = viewerPutToken("/api/view/${id4}?molochRegressionUser=test1", '{"name": "view4", "expression": "ip == 4.3.2.1", "roles":["arkimeUser"], "users":"", "editRoles":["cont3xtUser"], "user":"asdf"}', $token);
ok(!$info->{success}, "cannot transfer ownership to an invalid user");
eq_or_diff($info->{text}, "Invalid user: asdf");

# can transfer ownership
$info = viewerPutToken("/api/view/${id4}?molochRegressionUser=test1", '{"name": "view4", "expression": "ip == 4.3.2.1", "roles":["arkimeUser"], "users":"", "editRoles":["cont3xtUser"], "user":"test2"}', $token);
ok($info->{success}, "cannot transfer ownership to an invalid user");
eq_or_diff($info->{view}->{user}, "test2");

# test2 can delete view using editRoles (plus bonus cleanup)
viewerDeleteToken("/api/view/${id4}?molochRegressionUser=test2", $token2);

Expand Down
24 changes: 22 additions & 2 deletions viewer/apiViews.js
Expand Up @@ -229,8 +229,28 @@ class ViewAPIs {
try {
const { body: dbView } = await Db.getView(req.params.id);

// can't update creator of the view or the id
view.user = dbView._source.user;
// transfer ownership of view (admin and creator only)
if (view.user?.length) { // check if user is valid before updating it
if (req.settingUser.userId !== dbView._source.user && !req.settingUser.hasRole('arkimeAdmin')) {
return res.serverError(403, 'Permission denied');
}

// comma/newline separated value -> array of values
let user = ArkimeUtil.commaOrNewlineStringToArray(view.user);
user = await User.validateUserIds(user);

if (user.invalidUsers?.length) {
return res.serverError(403, `Invalid user: ${user.invalidUsers[0]}`);
}

if (user.validUsers?.length) {
view.user = user.validUsers[0];
}
31453 marked this conversation as resolved.
Show resolved Hide resolved
} else {
view.user = dbView._source.user;
}

// can't update the id
if (view.id) { delete view.id; }

// comma/newline separated value -> array of values
Expand Down
53 changes: 51 additions & 2 deletions viewer/vueapp/src/components/settings/Views.vue
Expand Up @@ -122,6 +122,15 @@
</b-button>
<template
v-if="canEdit(item)">
<b-button
size="sm"
variant="info"
v-b-tooltip.hover
v-if="canTransfer(item)"
title="Transfer ownership of this view"
@click="openTransferView(item)">
<span class="fa fa-share fa-fw" />
</b-button>
<b-button
size="sm"
variant="danger"
Expand Down Expand Up @@ -274,6 +283,10 @@
</template> <!-- /modal footer -->
</b-modal> <!-- /new view form -->

<transfer-resource
@transfer-resource="submitTransferView"
/>

</div>
</template>

Expand All @@ -284,12 +297,14 @@ import UserService from '../../../../../common/vueapp/UserService';
// components
import MolochPaging from '../utils/Pagination';
import RoleDropdown from '../../../../../common/vueapp/RoleDropdown';
import TransferResource from '../../../../../common/vueapp/TransferResource';

export default {
name: 'Views',
components: {
MolochPaging,
RoleDropdown
RoleDropdown,
TransferResource
},
data () {
return {
Expand All @@ -310,7 +325,8 @@ export default {
},
recordsTotal: 0,
recordsFiltered: 0,
seeAll: false
seeAll: false,
transferView: undefined
};
},
props: {
Expand Down Expand Up @@ -370,6 +386,10 @@ export default {
(view.user && view.user === this.user.userId) ||
(view.editRoles && UserService.hasRole(this.user, view.editRoles.join(',')));
},
canTransfer (view) {
return this.user.roles.includes('arkimeAdmin') ||
(view.user && view.user === this.user.userId);
},
/* updates the roles on a view object from the RoleDropdown component */
updateViewRoles (roles, id) {
for (const view of this.views) {
Expand Down Expand Up @@ -407,6 +427,35 @@ export default {
this.viewFormError = error.text;
});
},
/**
* Opens the transfer resource modal
* @param {Object} view The view to transfer
*/
openTransferView (view) {
this.transferView = view;
this.$bvModal.show('transfer-modal');
},
/**
* Submits the transfer resource modal contents and updates the view
* @param {Object} userId The user id to transfer the view to
*/
submitTransferView ({ userId }) {
if (!userId) {
this.transferView = undefined;
return;
}

const data = JSON.parse(JSON.stringify(this.transferView));
data.user = userId;

SettingsService.updateView(data, this.userId).then((response) => {
this.getViews();
this.transferView = undefined;
this.$emit('display-message', { msg: response.text, type: 'success' });
}).catch((error) => {
this.$emit('display-message', { msg: error.text, type: 'danger' });
});
},
/**
* Deletes a view given its name
* @param {Object} viewId The id of the view to delete
Expand Down