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

Feature/field level authentication #46

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

Danosaurus
Copy link
Collaborator

Field level authentication.

Added in the schema like so:

get schema() {
  return { 
    Field: { 
      ...
      canRead: (bundle) => {
        return Promise.resolve(!!bundle.isSudo);
      },
      canWriteOnCreate: (bundle) => {
        return Promise.resolve(!!bundle.isSudo);
      },
      canWriteOnUpdate: (bundle) => {
        return Promise.resolve(!!bundle.isSudo)
      }
    }
  }
}

@coveralls
Copy link

Coverage Status

Coverage increased (+0.2%) to 91.39% when pulling 451d549 on feature/field-level-authentication into 09881a3 on master.

1 similar comment
@coveralls
Copy link

coveralls commented Apr 18, 2017

Coverage Status

Coverage increased (+0.2%) to 91.39% when pulling 451d549 on feature/field-level-authentication into 09881a3 on master.

}
return permissionPromise;
})).then(authorizations => {
return Promise.resolve(_.reduce(authorizations, (acc, perm) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need _, JS natively supports reduce

permissionPromise = this.canWriteOnUpdate(bundle);
break;
default:
permissionPromise = Promise.resolve(true);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, shouldn't you throw instead ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would be better if we don't block access (by throwing) for permissions we don't support

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that's dangerous. Let's I mistype read to reed, I will never know my mistake

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, will change

}
return permissionPromise;
})).then(authorizations => {
return Promise.resolve(_.reduce(authorizations, (acc, perm) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And actually I'm not totally sure why you want to return a single value, how to know what error to throw ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now I display the permissions requested and the field accessed in the error. But I will make the error more granular.

}
FIELD_AUTH_METHODS.forEach(permCheck => {
if (schema[keyItem].hasOwnProperty(permCheck)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only check for canRead, defaultSelect has nothing to do with writing

}

let headers = bundle.req.headers['content-type'];
return Promise.reject(new Restypie.TemplateErrors.UnsupportedFormat({ expected: supported, value: headers }));
return Promise.resolve(parsed)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm ?? How can you get the bundle here ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

authorize returns the bundle that is passed in

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But Promise.resolve(parsed) doesn't ! :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clearer, what you get in then is just parsed, not the bundle

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ! parsed is a promise ? Maybe use a more descriptive naming, I got confused thinking it was an object. Also you can do parserPromise.then...

@@ -954,6 +976,49 @@ module.exports = class AbstractResource extends Restypie.Resources.AbstractCoreR
}

/**
* Authenticates the requested fields
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

authorizes ?

* @param fields
* @returns {Promise.<TResult>}
*/
authorize(bundle, fields) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this function only deals with selected fields ? The name might not be a good one then

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like you've tried to factorize, but it's making things a bit too unclear

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This deals with get/patch/post fields and the permission required is obtained through getPermissions

* @extends Restypie.TemplateErrors.AbstractError
* @constructor
*/
UnAuthorizedRequest: class UnAuthorizedRequestError extends AbstractError {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have FieldNotReadable, FieldNotWritable, FieldNotUpdatable

src/utils.js Outdated
makeArray(value) {
if (Array.isArray(value)) return value;
if (_.isUndefined(value)) return [];
return [value];
},

addIfNotInclude(array, item) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushUnique ?

src/utils.js Outdated
},

addIfNotInclude(array, item) {
if (!_.includes(array, item)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arrays natively support contains

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping lodash because native contains/includes breaks in different node versions

@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 91.585% when pulling d290fd3 on feature/field-level-authentication into 09881a3 on master.

1 similar comment
@coveralls
Copy link

coveralls commented Apr 19, 2017

Coverage Status

Coverage increased (+0.4%) to 91.585% when pulling d290fd3 on feature/field-level-authentication into 09881a3 on master.

return Promise.resolve(this._canWriteOnUpdate.call(null, bundle))
.then(result => {
if (!result) {
return Promise.reject(new Restypie.TemplateErrors.FieldNotUpdatable({
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rejecting here forces the override the throw the error as well. A way to fix this is add another private method of each of these that throws the error.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 91.647% when pulling 9d92a5c on feature/field-level-authentication into 09881a3 on master.

@coveralls
Copy link

coveralls commented Apr 19, 2017

Coverage Status

Coverage increased (+0.4%) to 91.647% when pulling ff85239 on feature/field-level-authentication into 09881a3 on master.

@coveralls
Copy link

coveralls commented Apr 20, 2017

Coverage Status

Coverage increased (+0.4%) to 91.647% when pulling 606700b on feature/field-level-authentication into 09881a3 on master.

@coveralls
Copy link

coveralls commented Apr 20, 2017

Coverage Status

Coverage increased (+0.4%) to 91.671% when pulling 54bdeb7 on feature/field-level-authentication into 09881a3 on master.

* @private
*/
_canWriteOnUpdate(bundle) {
return Promise.resolve(this.canWriteOnUpdate(bundle))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to wrap this into Promise.resolve ? Doesn't canWriteOnUpdate return a Promise already ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning a promise in case canWriteOnUpdate is overidden to return a value

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea !

return Promise.resolve(this.canRead(bundle))
.then(result => {
if (!result) {
return Promise.reject(new Restypie.TemplateErrors.FieldNotReadable({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just throw

key: this.key
}));
}
return Promise.resolve(result);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just return

}
});
if (this._canWriteOnCreateField) {
return Promise.resolve(this._canWriteOnCreateField.call(null, bundle));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you bind the context to null ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passed in because the context wasn't necessary, but I will add it in case it because useful later

@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 91.671% when pulling bc35a79 on feature/field-level-authentication into 09881a3 on master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 91.671% when pulling bc35a79 on feature/field-level-authentication into 09881a3 on master.

1 similar comment
@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 91.671% when pulling bc35a79 on feature/field-level-authentication into 09881a3 on master.

@coveralls
Copy link

coveralls commented Apr 21, 2017

Coverage Status

Coverage increased (+0.4%) to 91.651% when pulling e4a2b35 on feature/field-level-authentication into 09881a3 on master.

.add((bundle) => {
const includeScore = bundle.hasOption(Restypie.QueryOptions.INCLUDE_SCORE);
const scoreOnly = bundle.hasOption(Restypie.QueryOptions.SCORE_ONLY);
return Promise.resolve(resource.authorize(bundle)).then(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not to just add resource.authorize to the pipeline ?

.setStatusCode(Restypie.Codes.OK)
.next();
});
return Promise.resolve(resource.authorize(bundle))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

* @private
*/
_canWriteOnUpdate(bundle) {
return Promise.resolve(this.canWriteOnUpdate(bundle))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea !

return Promise.reject(new Restypie.TemplateErrors.UnsupportedFormat({ expected: supported, value: headers }));
}

return Promise.resolve(parserPromise)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return parserPromise.then... will do the same

return Promise.resolve(parserPromise)
.then(bundle => {
const data = Restypie.Utils.makeArray(bundle.body);
const fields = Object.getOwnPropertyNames(_.reduce(data, (acc, object) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What format are you trying to obtain ? There might be an easier way to get it

@@ -45,6 +45,14 @@ class AbstractForbiddenError extends AbstractError {
}
}

class AbstractUnauthorizedError extends AbstractError {
get statusCode() { return Restypie.Codes.Unauthorized; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confusion : Unauthorized actually means "Requires authentication". If the caller doesn't have the authorization to perform an operation, then the status code to send back is Forbidden. (I know...)

src/index.js Outdated
return null;
}
index = index || 0;
separator = separator || this.POPULATE_PATH_SEPARATOR;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

. is also used for deep filtering too. So you probably want this constants to be something like DEEP_PATH_SEPARATOR

@coveralls
Copy link

coveralls commented Apr 21, 2017

Coverage Status

Coverage increased (+0.4%) to 91.632% when pulling c51a7aa on feature/field-level-authentication into 09881a3 on master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants