mirror of
https://github.com/vale981/Vulcan
synced 2025-03-05 09:31:43 -05:00
permissions clean up & refactoring
This commit is contained in:
parent
3b5d4ea1ab
commit
c748bdc5bd
23 changed files with 182 additions and 244 deletions
69
README.md
69
README.md
|
@ -31,7 +31,7 @@ Note that both versions use the same data format, so you can go back and forth b
|
||||||
- [Forms](#forms)
|
- [Forms](#forms)
|
||||||
- [Methods](#methods)
|
- [Methods](#methods)
|
||||||
- [Routes](#routes)
|
- [Routes](#routes)
|
||||||
- [Groups](#routes)
|
- [Groups](#groups-permissions)
|
||||||
- [Internationalization](#internationalization)
|
- [Internationalization](#internationalization)
|
||||||
- [Cheatsheet](#cheatsheet)
|
- [Cheatsheet](#cheatsheet)
|
||||||
|
|
||||||
|
@ -485,16 +485,39 @@ Telescope.routes.add({
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Groups
|
## Groups & Permissions
|
||||||
|
|
||||||
User groups let you give your users permission to perform specific actions.
|
User groups let you give your users permission to perform specific actions.
|
||||||
|
|
||||||
### Creating A Group
|
To test if a user can perform an action, we don't check if they belong to a specific group (e.g. `user.isAdmin === true`), but instead if *at least one of the groups they belong to* has the rights to perform the current action.
|
||||||
|
|
||||||
|
### Permissions API
|
||||||
|
|
||||||
```js
|
```js
|
||||||
Users.createGroup("mods"); // let's create a new "mods" group
|
|
||||||
|
Users.createGroup(groupName); // create a new group
|
||||||
|
|
||||||
|
Users.methods.addGroup(userId, groupName); // add a user to a group (server only)
|
||||||
|
|
||||||
|
Users.getGroups(user); // get a list of all the groups a user belongs to
|
||||||
|
|
||||||
|
Users.getActions(user); // get a list of all the actions a user can perform
|
||||||
|
|
||||||
|
Users.canDo(user, action); // check if a user can perform a specific action
|
||||||
|
|
||||||
|
Users.canView(user, document); // shortcut to check if a user can view a specific document
|
||||||
|
|
||||||
|
Users.canEdit(user, document); // shortcut to check if a user can edit a specific document
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Documents can be Posts, Comments, or Users.
|
||||||
|
|
||||||
|
Note that some groups are applied automatically without having to call `addToGroup`:
|
||||||
|
|
||||||
|
- `anonymous`: any non-logged-in user is considered anonymous. This group is special in that anonymous users are by definition not part of any other group.
|
||||||
|
- `default`: default group for all existing users. Is applied to every user in addition to any other groups.
|
||||||
|
- `admins`: any user with the `isAdmin` flag set to true.
|
||||||
|
|
||||||
### Assigning Actions
|
### Assigning Actions
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
@ -502,7 +525,7 @@ Users.groups.mods.can("posts.edit.all"); // mods can edit anybody's posts
|
||||||
Users.groups.mods.can("posts.remove.all"); // mods can delete anybody's posts
|
Users.groups.mods.can("posts.remove.all"); // mods can delete anybody's posts
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also define and test for your own custom actions:
|
You can also define your own custom actions:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
Users.groups.mods.can("invite"); // new custom action
|
Users.groups.mods.can("invite"); // new custom action
|
||||||
|
@ -512,10 +535,18 @@ Here's a list of all out-of-the-box permissions:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// anonymous actions
|
// anonymous actions
|
||||||
posts.view
|
posts.view.approved.own
|
||||||
comments.view
|
posts.view.approved.all
|
||||||
|
comments.view.own
|
||||||
|
comments.view.all
|
||||||
|
|
||||||
// default actions
|
// default actions
|
||||||
|
posts.view.approved.own
|
||||||
|
posts.view.approved.all
|
||||||
|
posts.view.pending.own
|
||||||
|
posts.view.rejected.own
|
||||||
|
posts.view.spam.own
|
||||||
|
posts.view.deleted.own
|
||||||
posts.new
|
posts.new
|
||||||
posts.edit.own
|
posts.edit.own
|
||||||
posts.remove.own
|
posts.remove.own
|
||||||
|
@ -523,6 +554,8 @@ posts.upvote
|
||||||
posts.cancelUpvote
|
posts.cancelUpvote
|
||||||
posts.downvote
|
posts.downvote
|
||||||
posts.cancelDownvote
|
posts.cancelDownvote
|
||||||
|
comments.view.own
|
||||||
|
comments.view.all
|
||||||
comments.new
|
comments.new
|
||||||
comments.edit.own
|
comments.edit.own
|
||||||
comments.remove.own
|
comments.remove.own
|
||||||
|
@ -534,6 +567,10 @@ users.edit.own
|
||||||
users.remove.own
|
users.remove.own
|
||||||
|
|
||||||
// admin actions
|
// admin actions
|
||||||
|
posts.view.pending.all
|
||||||
|
posts.view.rejected.all
|
||||||
|
posts.view.spam.all
|
||||||
|
posts.view.deleted.all
|
||||||
posts.new.approved
|
posts.new.approved
|
||||||
posts.edit.all
|
posts.edit.all
|
||||||
posts.remove.all
|
posts.remove.all
|
||||||
|
@ -543,24 +580,6 @@ users.edit.all
|
||||||
users.remove.all
|
users.remove.all
|
||||||
```
|
```
|
||||||
|
|
||||||
### Groups API
|
|
||||||
|
|
||||||
```js
|
|
||||||
Users.methods.addGroup(userId, groupName); // add a user to a group (server only)
|
|
||||||
|
|
||||||
Users.getGroups(user); // get a list of all the groups a user belongs to
|
|
||||||
|
|
||||||
Users.getActions(user); // get a list of all the actions a user can perform
|
|
||||||
|
|
||||||
Users.canDo(user, action); // check if a user can perform a specific action
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that some groups are applied automatically without having to call `addToGroup`:
|
|
||||||
|
|
||||||
- `anonymous`: any non-logged-in user is considered anonymous.
|
|
||||||
- `admins`: any user with the `isAdmin` flag set to true.
|
|
||||||
- `default`: default group for all existing users.
|
|
||||||
|
|
||||||
## Internationalization
|
## Internationalization
|
||||||
|
|
||||||
Nova is internationalized using [react-intl](https://github.com/yahoo/react-intl/). To add a new language, you need to:
|
Nova is internationalized using [react-intl](https://github.com/yahoo/react-intl/). To add a new language, you need to:
|
||||||
|
|
|
@ -104,8 +104,8 @@ class CommentsItem extends Component{
|
||||||
<Telescope.components.UsersAvatar size="small" user={comment.user}/>
|
<Telescope.components.UsersAvatar size="small" user={comment.user}/>
|
||||||
<Telescope.components.UsersName user={comment.user}/>
|
<Telescope.components.UsersName user={comment.user}/>
|
||||||
<div className="comments-item-date"><FormattedRelative value={comment.postedAt}/></div>
|
<div className="comments-item-date"><FormattedRelative value={comment.postedAt}/></div>
|
||||||
{Users.can.edit(this.props.currentUser, this.props.comment) ? <a className="comment-edit" onClick={this.showEdit}><FormattedMessage id="comments.edit"/></a> : null}
|
{Users.canEdit(this.props.currentUser, this.props.comment) ? <a className="comment-edit" onClick={this.showEdit}><FormattedMessage id="comments.edit"/></a> : null}
|
||||||
{Users.can.edit(this.props.currentUser, this.props.comment) ? <a className="comment-delete" onClick={this.deleteComment}><FormattedMessage id="comments.delete"/></a> : null}
|
{Users.canEdit(this.props.currentUser, this.props.comment) ? <a className="comment-delete" onClick={this.deleteComment}><FormattedMessage id="comments.delete"/></a> : null}
|
||||||
</div>
|
</div>
|
||||||
{this.state.showEdit ? this.renderEdit() : this.renderComment()}
|
{this.state.showEdit ? this.renderEdit() : this.renderComment()}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { PropTypes, Component } from 'react';
|
||||||
import Users from 'meteor/nova:users';
|
import Users from 'meteor/nova:users';
|
||||||
|
|
||||||
const CanEditPost = ({user, post, children}) => {
|
const CanEditPost = ({user, post, children}) => {
|
||||||
if (Users.can.edit(user, post)) {
|
if (Users.canEdit(user, post)) {
|
||||||
return children;
|
return children;
|
||||||
} else if (!user){
|
} else if (!user){
|
||||||
return <p>Please log in.</p>;
|
return <p>Please log in.</p>;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Users from 'meteor/nova:users';
|
||||||
const CanEditUser = ({user, userToEdit, children}) => {
|
const CanEditUser = ({user, userToEdit, children}) => {
|
||||||
if (!user){
|
if (!user){
|
||||||
return <p>Please log in.</p>;
|
return <p>Please log in.</p>;
|
||||||
} else if (Users.can.edit(user, userToEdit)) {
|
} else if (Users.canEdit(user, userToEdit)) {
|
||||||
return children;
|
return children;
|
||||||
} else {
|
} else {
|
||||||
return <p>Sorry, you do not have permissions to edit this user at this time</p>;
|
return <p>Sorry, you do not have permissions to edit this user at this time</p>;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { PropTypes, Component } from 'react';
|
||||||
import Users from 'meteor/nova:users';
|
import Users from 'meteor/nova:users';
|
||||||
|
|
||||||
const CanView = ({user, children}) => {
|
const CanView = ({user, children}) => {
|
||||||
if (Users.can.view(user)) {
|
if (Users.canDo(user, "posts.view.approved.all")) {
|
||||||
return children;
|
return children;
|
||||||
} else if (!user){
|
} else if (!user){
|
||||||
return <p>Please log in.</p>;
|
return <p>Please log in.</p>;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { PropTypes, Component } from 'react';
|
||||||
import Users from 'meteor/nova:users';
|
import Users from 'meteor/nova:users';
|
||||||
|
|
||||||
const CanViewPost = ({user, post, children}) => {
|
const CanViewPost = ({user, post, children}) => {
|
||||||
if (Users.can.viewPost(this.props.user, this.props.document)) {
|
if (Users.canView(this.props.user, this.props.document)) {
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
} else if (!this.props.user){
|
} else if (!this.props.user){
|
||||||
return <p>Please log in.</p>;
|
return <p>Please log in.</p>;
|
||||||
|
|
|
@ -27,7 +27,7 @@ class PostsItem extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="post-actions">
|
<div className="post-actions">
|
||||||
{Users.can.edit(this.context.currentUser, this.props.post) ? component : ""}
|
{Users.canEdit(this.context.currentUser, this.props.post) ? component : ""}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,11 @@ import Users from 'meteor/nova:users';
|
||||||
import Categories from "../collection.js";
|
import Categories from "../collection.js";
|
||||||
|
|
||||||
Meteor.publish('categories', function() {
|
Meteor.publish('categories', function() {
|
||||||
if(Users.can.viewById(this.userId)){
|
|
||||||
|
const currentUser = this.userId && Users.findOne(this.userId);
|
||||||
|
|
||||||
|
if(Users.canDo(currentUser, "posts.view.approved.all")){
|
||||||
|
|
||||||
var categories = Categories.find();
|
var categories = Categories.find();
|
||||||
var publication = this;
|
var publication = this;
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ function CommentsNewSubmittedPropertiesCheck (comment, user) {
|
||||||
// ok
|
// ok
|
||||||
} else {
|
} else {
|
||||||
var field = schema[fieldName];
|
var field = schema[fieldName];
|
||||||
if (!Users.can.submitField(user, field)) {
|
if (!Users.canSubmitField (user, field)) {
|
||||||
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,7 +253,7 @@ Telescope.callbacks.add("comments.new.async", CommentsNewNotifications);
|
||||||
// ------------------------------------- comments.edit.method -------------------------------- //
|
// ------------------------------------- comments.edit.method -------------------------------- //
|
||||||
|
|
||||||
function CommentsEditUserCheck (modifier, comment, user) {
|
function CommentsEditUserCheck (modifier, comment, user) {
|
||||||
if (!user || !Users.can.edit(user, comment)) {
|
if (!user || !Users.canEdit(user, comment)) {
|
||||||
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_comment');
|
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_comment');
|
||||||
}
|
}
|
||||||
return modifier;
|
return modifier;
|
||||||
|
@ -269,7 +269,7 @@ function CommentsEditSubmittedPropertiesCheck (modifier, comment, user) {
|
||||||
_.keys(operation).forEach(function (fieldName) {
|
_.keys(operation).forEach(function (fieldName) {
|
||||||
|
|
||||||
var field = schema[fieldName];
|
var field = schema[fieldName];
|
||||||
if (!Users.can.editField(user, field, comment)) {
|
if (!Users.canEditField(user, field, comment)) {
|
||||||
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ Meteor.methods({
|
||||||
var comment = Comments.findOne(commentId);
|
var comment = Comments.findOne(commentId);
|
||||||
var user = Meteor.user();
|
var user = Meteor.user();
|
||||||
|
|
||||||
if(Users.can.edit(user, comment)){
|
if(Users.canEdit(user, comment)){
|
||||||
|
|
||||||
// decrement post comment count and remove user ID from post
|
// decrement post comment count and remove user ID from post
|
||||||
Posts.update(comment.postId, {
|
Posts.update(comment.postId, {
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import Users from 'meteor/nova:users';
|
import Users from 'meteor/nova:users';
|
||||||
|
|
||||||
const anonymousActions = [
|
const anonymousActions = [
|
||||||
"comments.view"
|
"comments.view.own",
|
||||||
|
"comments.view.all"
|
||||||
];
|
];
|
||||||
Users.groups.anonymous.can(anonymousActions);
|
Users.groups.anonymous.can(anonymousActions);
|
||||||
|
|
||||||
const defaultActions = [
|
const defaultActions = [
|
||||||
|
"comments.view.own",
|
||||||
|
"comments.view.all",
|
||||||
"comments.new",
|
"comments.new",
|
||||||
"comments.edit.own",
|
"comments.edit.own",
|
||||||
"comments.remove.own",
|
"comments.remove.own",
|
||||||
|
|
|
@ -10,7 +10,7 @@ Comments._ensureIndex({parentCommentId: 1});
|
||||||
*/
|
*/
|
||||||
Meteor.publish('comments.list', function (terms) {
|
Meteor.publish('comments.list', function (terms) {
|
||||||
|
|
||||||
const currentUser = Meteor.users.findOne(this.userId);
|
const currentUser = this.userId && Meteor.users.findOne(this.userId);
|
||||||
|
|
||||||
terms.currentUserId = this.userId; // add currentUserId to terms
|
terms.currentUserId = this.userId; // add currentUserId to terms
|
||||||
({selector, options} = Comments.parameters.get(terms));
|
({selector, options} = Comments.parameters.get(terms));
|
||||||
|
@ -24,7 +24,7 @@ Meteor.publish('comments.list', function (terms) {
|
||||||
const posts = Posts.find({_id: {$in: _.pluck(comments.fetch(), 'postId')}}, {fields: Posts.publishedFields.list});
|
const posts = Posts.find({_id: {$in: _.pluck(comments.fetch(), 'postId')}}, {fields: Posts.publishedFields.list});
|
||||||
const users = Meteor.users.find({_id: {$in: _.pluck(comments.fetch(), 'userId')}}, {fields: Users.publishedFields.list});
|
const users = Meteor.users.find({_id: {$in: _.pluck(comments.fetch(), 'userId')}}, {fields: Users.publishedFields.list});
|
||||||
|
|
||||||
return Users.can.view(currentUser) ? [comments, posts, users] : [];
|
return Users.canDo(currentUser, "comments.view.all") ? [comments, posts, users] : [];
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ Meteor.publish('comments.list', function (terms) {
|
||||||
// const childCommentIds = _.pluck(Comments.find({parentCommentId: terms._id}, {fields: {_id: 1}}).fetch(), '_id');
|
// const childCommentIds = _.pluck(Comments.find({parentCommentId: terms._id}, {fields: {_id: 1}}).fetch(), '_id');
|
||||||
// commentIds = commentIds.concat(childCommentIds);
|
// commentIds = commentIds.concat(childCommentIds);
|
||||||
|
|
||||||
// return Users.can.view(currentUser) ? Comments.find({_id: {$in: commentIds}}, {sort: {score: -1, postedAt: -1}}) : [];
|
// return Users.canView(currentUser) ? Comments.find({_id: {$in: commentIds}}, {sort: {score: -1, postedAt: -1}}) : [];
|
||||||
|
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ Meteor.publish('comments.list', function (terms) {
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
// if(Users.can.viewById(this.userId)){
|
// if(Users.canViewById(this.userId)){
|
||||||
// var comment = Comments.findOne(commentId);
|
// var comment = Comments.findOne(commentId);
|
||||||
// return Posts.find({_id: comment && comment.postId});
|
// return Posts.find({_id: comment && comment.postId});
|
||||||
// }
|
// }
|
||||||
|
@ -79,7 +79,7 @@ Meteor.publish('comments.list', function (terms) {
|
||||||
|
|
||||||
// var userIds = [];
|
// var userIds = [];
|
||||||
|
|
||||||
// if(Users.can.viewById(this.userId)){
|
// if(Users.canViewById(this.userId)){
|
||||||
|
|
||||||
// var comment = Comments.findOne(commentId);
|
// var comment = Comments.findOne(commentId);
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@ Meteor.methods({
|
||||||
},
|
},
|
||||||
generateThumbnail: function (post) {
|
generateThumbnail: function (post) {
|
||||||
check(post, Posts.simpleSchema());
|
check(post, Posts.simpleSchema());
|
||||||
if (Users.can.edit(Meteor.user(), post)) {
|
if (Users.canEdit(Meteor.user(), post)) {
|
||||||
regenerateThumbnail(post);
|
regenerateThumbnail(post);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,8 @@ Meteor.methods({
|
||||||
return Newsletter.scheduleNextWithMailChimp(true);
|
return Newsletter.scheduleNextWithMailChimp(true);
|
||||||
},
|
},
|
||||||
'newsletter.addUser'(user){
|
'newsletter.addUser'(user){
|
||||||
if (!user || !Users.can.editById(this.userId, user)) {
|
const currentUser = this.userId && Users.findOne(this.userId);
|
||||||
|
if (!user || !Users.canEdit(currentUser, user)) {
|
||||||
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
|
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +24,8 @@ Meteor.methods({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'newsletter.removeUser'(user) {
|
'newsletter.removeUser'(user) {
|
||||||
if (!user || !Users.can.editById(this.userId, user)) {
|
const currentUser = this.userId && Users.findOne(this.userId);
|
||||||
|
if (!user || !Users.canEdit(currentUser, user)) {
|
||||||
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
|
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,7 @@ function PostsNewSubmittedPropertiesCheck (post, user) {
|
||||||
_.keys(post).forEach(function (fieldName) {
|
_.keys(post).forEach(function (fieldName) {
|
||||||
|
|
||||||
var field = schema[fieldName];
|
var field = schema[fieldName];
|
||||||
if (!Users.can.submitField(user, field)) {
|
if (!Users.canSubmitField (user, field)) {
|
||||||
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,7 +283,7 @@ Telescope.callbacks.add("posts.new.async", PostsNewNotifications);
|
||||||
|
|
||||||
function PostsEditUserCheck (modifier, post, user) {
|
function PostsEditUserCheck (modifier, post, user) {
|
||||||
// check that user can edit document
|
// check that user can edit document
|
||||||
if (!user || !Users.can.edit(user, post)) {
|
if (!user || !Users.canEdit(user, post)) {
|
||||||
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_post');
|
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_post');
|
||||||
}
|
}
|
||||||
return modifier;
|
return modifier;
|
||||||
|
@ -299,7 +299,7 @@ function PostsEditSubmittedPropertiesCheck (modifier, post, user) {
|
||||||
_.keys(operation).forEach(function (fieldName) {
|
_.keys(operation).forEach(function (fieldName) {
|
||||||
|
|
||||||
var field = schema[fieldName];
|
var field = schema[fieldName];
|
||||||
if (!Users.can.editField(user, field, post)) {
|
if (!Users.canEditField(user, field, post)) {
|
||||||
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -179,36 +179,3 @@ ${Posts.getLink(post, true, false)}
|
||||||
};
|
};
|
||||||
Posts.helpers({ getEmailShareUrl() { return Posts.getEmailShareUrl(this); } });
|
Posts.helpers({ getEmailShareUrl() { return Posts.getEmailShareUrl(this); } });
|
||||||
|
|
||||||
|
|
||||||
///////////////////
|
|
||||||
// Users Helpers //
|
|
||||||
///////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Check if a given user can view a specific post
|
|
||||||
* @param {Object} user - can be undefined!
|
|
||||||
* @param {Object} post
|
|
||||||
*/
|
|
||||||
Users.can.viewPost = function (user, post) {
|
|
||||||
|
|
||||||
if (Users.is.admin(user)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
switch (post.status) {
|
|
||||||
|
|
||||||
case Posts.config.STATUS_APPROVED:
|
|
||||||
return Users.can.view(user);
|
|
||||||
|
|
||||||
case Posts.config.STATUS_REJECTED:
|
|
||||||
case Posts.config.STATUS_SPAM:
|
|
||||||
case Posts.config.STATUS_PENDING:
|
|
||||||
return Users.can.view(user) && Users.is.owner(user, post);
|
|
||||||
|
|
||||||
case Posts.config.STATUS_DELETED:
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Users.helpers({canViewPost: function () {return Users.can.viewPost(this, post);}});
|
|
||||||
|
|
|
@ -215,7 +215,7 @@ Meteor.methods({
|
||||||
|
|
||||||
var post = Posts.findOne({_id: postId});
|
var post = Posts.findOne({_id: postId});
|
||||||
|
|
||||||
if (!Meteor.userId() || !Users.can.editById(Meteor.userId(), post)){
|
if (!Meteor.userId() || !Users.canEdit(Meteor.user(), post)){
|
||||||
throw new Meteor.Error(606, 'You need permission to edit or delete a post');
|
throw new Meteor.Error(606, 'You need permission to edit or delete a post');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import Users from 'meteor/nova:users';
|
import Users from 'meteor/nova:users';
|
||||||
|
|
||||||
const anonymousActions = [
|
const anonymousActions = [
|
||||||
"posts.view"
|
"posts.view.approved.own",
|
||||||
|
"posts.view.approved.all"
|
||||||
];
|
];
|
||||||
Users.groups.anonymous.can(anonymousActions);
|
Users.groups.anonymous.can(anonymousActions);
|
||||||
|
|
||||||
const defaultActions = [
|
const defaultActions = [
|
||||||
"posts.view",
|
"posts.view.approved.own",
|
||||||
|
"posts.view.approved.all",
|
||||||
|
"posts.view.pending.own",
|
||||||
|
"posts.view.rejected.own",
|
||||||
|
"posts.view.spam.own",
|
||||||
|
"posts.view.deleted.own",
|
||||||
"posts.new",
|
"posts.new",
|
||||||
"posts.edit.own",
|
"posts.edit.own",
|
||||||
"posts.remove.own",
|
"posts.remove.own",
|
||||||
|
@ -18,8 +24,12 @@ const defaultActions = [
|
||||||
Users.groups.default.can(defaultActions);
|
Users.groups.default.can(defaultActions);
|
||||||
|
|
||||||
const adminActions = [
|
const adminActions = [
|
||||||
|
"posts.view.pending.all",
|
||||||
|
"posts.view.rejected.all",
|
||||||
|
"posts.view.spam.all",
|
||||||
|
"posts.view.deleted.all",
|
||||||
"posts.new.approved",
|
"posts.new.approved",
|
||||||
"posts.edit.all",
|
"posts.edit.all",
|
||||||
"posts.remove.all"
|
"posts.remove.all"
|
||||||
];
|
];
|
||||||
Users.groups.admins.can(adminActions);
|
Users.groups.admins.can(adminActions);
|
||||||
|
|
|
@ -70,7 +70,7 @@ Meteor.publish('posts.list', function (terms) {
|
||||||
|
|
||||||
this.autorun(function () {
|
this.autorun(function () {
|
||||||
|
|
||||||
const currentUser = Meteor.users.findOne(this.userId);
|
const currentUser = this.userId && Meteor.users.findOne(this.userId);
|
||||||
|
|
||||||
terms.currentUserId = this.userId; // add currentUserId to terms
|
terms.currentUserId = this.userId; // add currentUserId to terms
|
||||||
const {selector, options} = Posts.parameters.get(terms);
|
const {selector, options} = Posts.parameters.get(terms);
|
||||||
|
@ -88,7 +88,7 @@ Meteor.publish('posts.list', function (terms) {
|
||||||
return getPostsListUsers(posts);
|
return getPostsListUsers(posts);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Users.can.view(currentUser) ? [posts, users] : [];
|
return Users.canDo(currentUser, "posts.view.approved.all") ? [posts, users] : [];
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -102,14 +102,14 @@ Meteor.publish('posts.single', function (terms) {
|
||||||
|
|
||||||
check(terms, Match.OneOf({_id: String}, {_id: String, slug: Match.Any}));
|
check(terms, Match.OneOf({_id: String}, {_id: String, slug: Match.Any}));
|
||||||
|
|
||||||
const currentUser = Meteor.users.findOne(this.userId);
|
const currentUser = this.userId && Meteor.users.findOne(this.userId);
|
||||||
const options = {fields: Posts.publishedFields.single};
|
const options = {fields: Posts.publishedFields.single};
|
||||||
const posts = Posts.find(terms._id, options);
|
const posts = Posts.find(terms._id, options);
|
||||||
const post = posts.fetch()[0];
|
const post = posts.fetch()[0];
|
||||||
|
|
||||||
if (post) {
|
if (post) {
|
||||||
const users = getSinglePostUsers(post);
|
const users = getSinglePostUsers(post);
|
||||||
return Users.can.viewPost(currentUser, post) ? [posts, users] : [];
|
return Users.canView(currentUser, post) ? [posts, users] : [];
|
||||||
} else {
|
} else {
|
||||||
console.log(`// posts.single: no post found for _id “${terms._id}”`)
|
console.log(`// posts.single: no post found for _id “${terms._id}”`)
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import Users from './collection.js';
|
import Users from './collection.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Users.groups object
|
||||||
|
*/
|
||||||
|
Users.groups = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Group class
|
* @summary Group class
|
||||||
*/
|
*/
|
||||||
|
@ -21,11 +26,6 @@ class Group {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Users.groups object
|
|
||||||
*/
|
|
||||||
Users.groups = {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary create a new group
|
* @summary create a new group
|
||||||
* @param {String} groupName
|
* @param {String} groupName
|
||||||
|
@ -63,7 +63,6 @@ Users.getGroups = user => {
|
||||||
return userGroups;
|
return userGroups;
|
||||||
|
|
||||||
};
|
};
|
||||||
Users.helpers({getGroups: function () {return Users.getGroups(this);}});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary get a list of all the actions a user can perform
|
* @summary get a list of all the actions a user can perform
|
||||||
|
@ -74,7 +73,6 @@ Users.getActions = user => {
|
||||||
const groupActions = userGroups.map(groupName => Users.groups[groupName].actions);
|
const groupActions = userGroups.map(groupName => Users.groups[groupName].actions);
|
||||||
return _.unique(_.flatten(groupActions));
|
return _.unique(_.flatten(groupActions));
|
||||||
};
|
};
|
||||||
Users.helpers({getActions: function () {return Users.getActions(this);}});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary check if a user can perform a specific action
|
* @summary check if a user can perform a specific action
|
||||||
|
@ -84,7 +82,72 @@ Users.helpers({getActions: function () {return Users.getActions(this);}});
|
||||||
Users.canDo = (user, action) => {
|
Users.canDo = (user, action) => {
|
||||||
return Users.getActions(user).indexOf(action) !== -1;
|
return Users.getActions(user).indexOf(action) !== -1;
|
||||||
};
|
};
|
||||||
Users.helpers({canDo: function (action) {return Users.canDo(this, action);}});
|
|
||||||
|
/**
|
||||||
|
* @summary Check if a given user can view a specific document
|
||||||
|
* @param {Object} user - can be undefined!
|
||||||
|
* @param {Object} document - Note: only actually works with posts for now
|
||||||
|
*/
|
||||||
|
Users.canView = function (user, document) {
|
||||||
|
|
||||||
|
const status = _.findWhere(Posts.config.postStatuses, {value: document.status}).label;
|
||||||
|
const collectionName = document.getCollectionName();
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Users.owns(user, document)) {
|
||||||
|
return Users.canDo(user, `${collectionName}.view.${status}.own`);
|
||||||
|
} else {
|
||||||
|
return Users.canDo(user, `${collectionName}.view.${status}.all`);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Check if a user can edit a document
|
||||||
|
* @param {Object} user - The user performing the action
|
||||||
|
* @param {Object} document - The document being edited
|
||||||
|
*/
|
||||||
|
Users.canEdit = function (user, document) {
|
||||||
|
|
||||||
|
user = (typeof user === 'undefined') ? Meteor.user() : user;
|
||||||
|
const collectionName = document.getCollectionName();
|
||||||
|
|
||||||
|
if (!user || !document) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.hasOwnProperty('isDeleted') && document.isDeleted) return false;
|
||||||
|
|
||||||
|
if (Users.owns(user, document)) {
|
||||||
|
// if this is user's document, check if user can edit own documents
|
||||||
|
return Users.canDo(user, `${collectionName}.edit.own`);
|
||||||
|
} else {
|
||||||
|
// if this is not user's document, check if they can edit all documents
|
||||||
|
return Users.canDo(user, `${collectionName}.edit.all`);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Check if a user can submit a field
|
||||||
|
* @param {Object} user - The user performing the action
|
||||||
|
* @param {Object} field - The field being edited or inserted
|
||||||
|
*/
|
||||||
|
Users.canSubmitField = function (user, field) {
|
||||||
|
return user && field.insertableIf && field.insertableIf(user);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @function
|
||||||
|
* Check if a user can edit a field – for now, identical to Users.canSubmitField
|
||||||
|
* @param {Object} user - The user performing the action
|
||||||
|
* @param {Object} field - The field being edited or inserted
|
||||||
|
*/
|
||||||
|
Users.canEditField = function (user, field, document) {
|
||||||
|
return user && field.editableIf && field.editableIf(user, document);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary initialize the 3 out-of-the-box groups
|
* @summary initialize the 3 out-of-the-box groups
|
||||||
|
@ -92,20 +155,3 @@ Users.helpers({canDo: function (action) {return Users.canDo(this, action);}});
|
||||||
Users.createGroup("anonymous"); // non-logged-in users
|
Users.createGroup("anonymous"); // non-logged-in users
|
||||||
Users.createGroup("default"); // regular users
|
Users.createGroup("default"); // regular users
|
||||||
Users.createGroup("admins"); // admin users
|
Users.createGroup("admins"); // admin users
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary add default actions concerning users
|
|
||||||
*/
|
|
||||||
const defaultActions = [
|
|
||||||
"users.edit.own",
|
|
||||||
"users.remove.own"
|
|
||||||
];
|
|
||||||
Users.groups.default.can(defaultActions);
|
|
||||||
|
|
||||||
const adminActions = [
|
|
||||||
"users.edit.all",
|
|
||||||
"users.remove.all"
|
|
||||||
];
|
|
||||||
Users.groups.admins.can(adminActions);
|
|
||||||
|
|
||||||
console.log(Users.groups);
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ Meteor.methods({
|
||||||
// ------------------------------ Checks ------------------------------ //
|
// ------------------------------ Checks ------------------------------ //
|
||||||
|
|
||||||
// check that user can edit document
|
// check that user can edit document
|
||||||
if (!user || !Users.can.edit(currentUser, user)) {
|
if (!user || !Users.canEdit(currentUser, user)) {
|
||||||
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
|
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ Meteor.methods({
|
||||||
// loop over each property being operated on
|
// loop over each property being operated on
|
||||||
_.keys(operation).forEach(function (fieldName) {
|
_.keys(operation).forEach(function (fieldName) {
|
||||||
var field = schema[fieldName];
|
var field = schema[fieldName];
|
||||||
if (!Users.can.editField(user, field, user)) {
|
if (!Users.canEditField(user, field, user)) {
|
||||||
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ Meteor.methods({
|
||||||
// ------------------------------ Checks ------------------------------ //
|
// ------------------------------ Checks ------------------------------ //
|
||||||
|
|
||||||
// check that user can edit document
|
// check that user can edit document
|
||||||
if (!user || !Users.can.edit(currentUser, user)) {
|
if (!user || !Users.canEdit(currentUser, user)) {
|
||||||
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
|
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ Meteor.methods({
|
||||||
_.keys(operation).forEach(function (fieldName) {
|
_.keys(operation).forEach(function (fieldName) {
|
||||||
|
|
||||||
var field = schema[fieldName];
|
var field = schema[fieldName];
|
||||||
if (!Users.can.editField(currentUser, field, user)) {
|
if (!Users.canEditField(currentUser, field, user)) {
|
||||||
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
throw new Meteor.Error("disallowed_property", 'disallowed_property_detected' + ": " + fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +166,7 @@ Meteor.methods({
|
||||||
user = Users.findOne(userId);
|
user = Users.findOne(userId);
|
||||||
|
|
||||||
// check that user can edit document
|
// check that user can edit document
|
||||||
if (!user || !Users.can.edit(currentUser, user)) {
|
if (!user || !Users.canEdit(currentUser, user)) {
|
||||||
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
|
throw new Meteor.Error(601, 'sorry_you_cannot_edit_this_user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import Users from './collection.js';
|
||||||
import './roles.js';
|
import './roles.js';
|
||||||
import './schema.js';
|
import './schema.js';
|
||||||
import './config.js';
|
import './config.js';
|
||||||
import './permissions.js';
|
|
||||||
import './collection.js';
|
import './collection.js';
|
||||||
import './callbacks.js';
|
import './callbacks.js';
|
||||||
import './helpers.js';
|
import './helpers.js';
|
||||||
|
@ -13,5 +12,6 @@ import './emails.js';
|
||||||
import './avatar.js';
|
import './avatar.js';
|
||||||
import './methods.js';
|
import './methods.js';
|
||||||
import './groups.js';
|
import './groups.js';
|
||||||
|
import './permissions.js';
|
||||||
|
|
||||||
export default Users;
|
export default Users;
|
|
@ -1,126 +1,13 @@
|
||||||
import Users from './collection.js';
|
import Users from './collection.js';
|
||||||
|
|
||||||
// note: using collection helpers here is probably a bad idea,
|
const defaultActions = [
|
||||||
// because they'll throw an error when the user is undefined
|
"users.edit.own",
|
||||||
|
"users.remove.own"
|
||||||
/**
|
];
|
||||||
* @summary Telescope permissions
|
Users.groups.default.can(defaultActions);
|
||||||
* @namespace Users.can
|
|
||||||
*/
|
|
||||||
Users.can = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Check if a given user has access to view posts
|
|
||||||
* @param {Object} user
|
|
||||||
*/
|
|
||||||
Users.can.view = function (user) {
|
|
||||||
if (Telescope.settings.get('requireViewInvite', false)) {
|
|
||||||
|
|
||||||
if (Meteor.isClient) {
|
|
||||||
// on client only, default to the current user
|
|
||||||
user = (typeof user === 'undefined') ? Meteor.user() : user;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (!!user && (Users.is.admin(user) || Users.is.invited(user)));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
Users.helpers({canView: function () {return Users.can.view(this);}});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Check if a given user can view a specific post
|
|
||||||
* @param {Object} user
|
|
||||||
* @param {Object} post
|
|
||||||
*/
|
|
||||||
Users.can.viewById = function (userId) {
|
|
||||||
// if an invite is required to view, run permission check, else return true
|
|
||||||
if (Telescope.settings.get('requireViewInvite', false)) {
|
|
||||||
return !!userId ? Users.can.view(Meteor.users.findOne(userId)) : false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
Users.helpers({canViewById: function () {return Users.can.viewById(this);}});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Check if a given user has permission to submit new posts
|
|
||||||
* @param {Object} user
|
|
||||||
*/
|
|
||||||
// Users.can.post = function (user) {
|
|
||||||
// return Users.canDo(user, "posts.new");
|
|
||||||
// };
|
|
||||||
// Users.helpers({canPost: function () {return Users.can.post(this);}});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Check if a given user has permission to comment (same as posting for now)
|
|
||||||
* @param {Object} user
|
|
||||||
*/
|
|
||||||
// Users.can.comment = function (user) {
|
|
||||||
// return Users.canDo(user, "comments.new");
|
|
||||||
// };
|
|
||||||
// Users.helpers({canComment: function () {return Users.can.comment(this);}});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Check if a user has permission to vote (same as posting for now)
|
|
||||||
* @param {Object} user
|
|
||||||
*/
|
|
||||||
// Users.can.vote = function (user) {
|
|
||||||
// return Users.can.post(user);
|
|
||||||
// };
|
|
||||||
// Users.helpers({canVote: function () {return Users.can.vote(this);}});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Check if a user can edit a document
|
|
||||||
* @param {Object} user - The user performing the action
|
|
||||||
* @param {Object} document - The document being edited
|
|
||||||
*/
|
|
||||||
Users.can.edit = function (user, document) {
|
|
||||||
|
|
||||||
user = (typeof user === 'undefined') ? Meteor.user() : user;
|
|
||||||
const collectionName = document.getCollectionName();
|
|
||||||
|
|
||||||
if (!user || !document) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.hasOwnProperty('isDeleted') && document.isDeleted) return false;
|
|
||||||
|
|
||||||
// if this is user's document, check if user can edit own documents
|
|
||||||
const editOwnCheck = user.owns(document) && user.canDo(collectionName+".edit.own");
|
|
||||||
|
|
||||||
// if this is not user's document, check if they can edit all documents
|
|
||||||
const editAllCheck = user.canDo(collectionName+".edit.all");
|
|
||||||
|
|
||||||
return editOwnCheck || editAllCheck;
|
|
||||||
};
|
|
||||||
Users.helpers({canEdit: function (document) {return Users.can.edit(this, document);}});
|
|
||||||
|
|
||||||
Users.can.editById = function (userId, document) {
|
|
||||||
var user = Meteor.users.findOne(userId);
|
|
||||||
return Users.can.edit(user, document);
|
|
||||||
};
|
|
||||||
Users.helpers({canEditById: function (document) {return Users.can.editById(this, document);}});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Check if a user can submit a field
|
|
||||||
* @param {Object} user - The user performing the action
|
|
||||||
* @param {Object} field - The field being edited or inserted
|
|
||||||
*/
|
|
||||||
Users.can.submitField = function (user, field) {
|
|
||||||
return user && field.insertableIf && field.insertableIf(user);
|
|
||||||
};
|
|
||||||
Users.helpers({canSubmitField: function (field) {return Users.can.submitField(this, field);}});
|
|
||||||
|
|
||||||
/** @function
|
|
||||||
* Check if a user can edit a field – for now, identical to Users.can.submitField
|
|
||||||
* @param {Object} user - The user performing the action
|
|
||||||
* @param {Object} field - The field being edited or inserted
|
|
||||||
*/
|
|
||||||
Users.can.editField = function (user, field, document) {
|
|
||||||
return user && field.editableIf && field.editableIf(user, document);
|
|
||||||
};
|
|
||||||
|
|
||||||
Users.can.invite = function (user) {
|
|
||||||
return Users.is.invited(user) || Users.is.admin(user);
|
|
||||||
};
|
|
||||||
Users.helpers({canInvite: function () {return Users.can.invite(this);}});
|
|
||||||
|
|
||||||
|
const adminActions = [
|
||||||
|
"users.edit.all",
|
||||||
|
"users.remove.all"
|
||||||
|
];
|
||||||
|
Users.groups.admins.can(adminActions);
|
Loading…
Add table
Reference in a new issue