-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Initial relation auth implementation #1390
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
Conversation
|
||
var modelName = Model.modelName; | ||
|
||
var modelSettings = Model.settings || {}; | ||
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401; | ||
if (!req.accessToken) { | ||
if (req.accessToken) { | ||
ctx.accessToken = req.accessToken; |
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 doesn't seem like the right place for this, but it is required to be able to just pass the ctx
to the auth hook.
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.
Well, is it necessary for the auth hook to share the ctx
object with strong-remoting? I mean you don't have to pass strong-remoting context to the auth hook, in my opinion you are free to create your own auth-hook-specific context.
Yes, I agree we must be very careful here. I think it's very likely that there are projects which actually depend on the current behaviour. Can we hide this new behaviour behind a "compat" flag, which would be disabled by default, but enabled in the project template in loopback-workspace? |
req.body && req.body.id !== undefined ? req.body.id : | ||
req.query && req.query.id !== undefined ? req.query.id : | ||
undefined); | ||
var modelId = modelInstance && modelInstance.id || ctx.args.id; |
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 don't remember how exactly does strong-remoting handle arguments of prototype method. I think ctx.args.id
is set for the shared constructor only, the method itself does not necessarily have to have an id
argument.
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.
... In which case this may be a breaking change.
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.
Hmmm... thats right! I can add in the sharedCtor args to the remote context but I'm not sure that is useful. But I'm not happy with the current mess of param checking (which is http specific for no reason).
In general, it would be great to write several tests for each relation method and check that
Preferably one test for each cause of "unauthorized user". Using your basic example outlined in the PR description, there are three permissions being checked:
So you need four tests for this particular relation method:
I think the important part is to come up with a good DSL that will allow you to write this tests with minimum amount of duplicated code. Perhaps something similar to what we have in test/access-control.integration.js. Or you can invent a new language, for example: describe('hasMany relation methods', function() {
describe('find', function() {
it('allows users with all permissions', function(done) {
requestWithPermissions({ Customer: 'READ', Review: 'READ', FOO: 'READ' })
.get('/customers/1/reviews?include=foo')
.expect(200).end(done);
});
it('rejects user without permission to read related model', function(done) {
requestWithPermissions({ Customer: 'READ', Review: null, FOO: 'READ' })
.get('/customers/1/reviews?include=foo')
.expect(403).end(done);
});
// etc.
});
});
function requestWithPermissions(map) {
// create a new user
// setup ACLs to give the user permissions from "map"
// login the new user
// setup supertest object with Authorization header set to user's access token
} Or you can even push this further: describe('hasMany relation methods', function() {
describe('find', function() {
describe('/customers/1/reviews?include=foo', function() {
verifyAccessCheck({ Customer: 'READ', Review: 'READ', FOO: 'READ' }, 200);
verifyAccessCheck({ Customer: 'READ', Review: null, FOO: 'READ' }, 403);
});
});
}); |
The testing comment is really useful thanks @bajtos |
@ritch looking forward to this - are there any remaining issues? |
Yes... We need to re-work this once #1495 is implemented as a core feature. |
Is there an update on this? |
/to @bajtos
/cc @raymondfeng
WIP: DO NOT MERGE
This could be considered a controversial change. It changes the behavior for access control of the dynamically defined relation remote methods.
Here is a basic example:
/customers/1/reviews?include=foo
=>Customers.prototype.__get__reviews()
checkAccess()
for customer with id 1checkAccess()
forReviews.find()
checkAccess()
forFoo.find()
This same concept is applied to all other relation methods.
Where I am a bit concerned about this change... what if you don't want to check access in the way mentioned above?
....well technically you can modify the auth hook (or just remove it) like this
@bajtos do you share the concern above? I don't want to break existing apps unless it is in a helpful way (eg. patching a security gap). Also can you look at the implementation and suggest how to test it or any major changes? I am going to actually start over since I am not entirely happy with how it is implemented (very duplicative).
Alternative Impl
I'm thinking of passing all the hooks through
Model.checkRelationAccess
which would allow you to change the way the check is performed just by overriding that function.Connect #1362