-
Notifications
You must be signed in to change notification settings - Fork 7
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
base: master
Are you sure you want to change the base?
Conversation
1 similar comment
src/fields/lib/abstract-field.js
Outdated
} | ||
return permissionPromise; | ||
})).then(authorizations => { | ||
return Promise.resolve(_.reduce(authorizations, (acc, perm) => { |
There was a problem hiding this comment.
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
src/fields/lib/abstract-field.js
Outdated
permissionPromise = this.canWriteOnUpdate(bundle); | ||
break; | ||
default: | ||
permissionPromise = Promise.resolve(true); |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, will change
src/fields/lib/abstract-field.js
Outdated
} | ||
return permissionPromise; | ||
})).then(authorizations => { | ||
return Promise.resolve(_.reduce(authorizations, (acc, perm) => { |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ! :)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
src/template-errors.js
Outdated
* @extends Restypie.TemplateErrors.AbstractError | ||
* @constructor | ||
*/ | ||
UnAuthorizedRequest: class UnAuthorizedRequestError extends AbstractError { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arrays natively support contains
There was a problem hiding this comment.
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
1 similar comment
src/fields/lib/abstract-field.js
Outdated
return Promise.resolve(this._canWriteOnUpdate.call(null, bundle)) | ||
.then(result => { | ||
if (!result) { | ||
return Promise.reject(new Restypie.TemplateErrors.FieldNotUpdatable({ |
There was a problem hiding this comment.
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.
…inEstevez/Restypie into feature/field-level-authentication
* @private | ||
*/ | ||
_canWriteOnUpdate(bundle) { | ||
return Promise.resolve(this.canWriteOnUpdate(bundle)) |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea !
src/fields/lib/abstract-field.js
Outdated
return Promise.resolve(this.canRead(bundle)) | ||
.then(result => { | ||
if (!result) { | ||
return Promise.reject(new Restypie.TemplateErrors.FieldNotReadable({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just throw
src/fields/lib/abstract-field.js
Outdated
key: this.key | ||
})); | ||
} | ||
return Promise.resolve(result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just return
src/fields/lib/abstract-field.js
Outdated
} | ||
}); | ||
if (this._canWriteOnCreateField) { | ||
return Promise.resolve(this._canWriteOnCreateField.call(null, bundle)); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
1 similar comment
src/basic-routes/lib/get-many.js
Outdated
.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(() => { |
There was a problem hiding this comment.
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 ?
src/basic-routes/lib/get-single.js
Outdated
.setStatusCode(Restypie.Codes.OK) | ||
.next(); | ||
}); | ||
return Promise.resolve(resource.authorize(bundle)) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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
src/template-errors.js
Outdated
@@ -45,6 +45,14 @@ class AbstractForbiddenError extends AbstractError { | |||
} | |||
} | |||
|
|||
class AbstractUnauthorizedError extends AbstractError { | |||
get statusCode() { return Restypie.Codes.Unauthorized; } |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
Field level authentication.
Added in the schema like so: