From 5097fc283c366f596c231eef7b8b7ce89e17212b Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Sun, 7 Oct 2012 19:02:20 -0700 Subject: [PATCH] Document allow/deny. --- docs/client/api.html | 138 ++++++++++++++++++++++++++++++++++++++++++- docs/client/api.js | 24 +++++--- docs/client/docs.css | 4 ++ 3 files changed, 154 insertions(+), 12 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 385515606b..ca8da6c1ab 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -203,6 +203,8 @@ object, which provides the following: * `isSimulation`: a boolean value, true if this invocation is a stub. * `unblock`: when called, allows the next method from this client to begin running. +* `userId`: a function that returns the id of the current user. +* `setUserId`: a function that associates the current client with a user. Calling `methods` on the client defines *stub* functions associated with server methods of the same name. You don't have to define a stub for @@ -217,10 +219,23 @@ intended to *simulate* the result of what the server's method will do, but without waiting for the round trip delay. If a stub throws an exception it will be logged to the console. + {{> api_box method_invocation_userId}} +The user for a method is determined when the method starts running and +will not change during method execution, even if `unblock` is called. + {{> api_box method_invocation_setUserId}} +Calling this function from a method will result in all future methods +received on the same connection having their `userId` set to the +provided value. Future calls to `this.userId` in the same method will +also return the new value. + +Values should correspond to the `_id` field of a document in the +`Meteor.users` collection. + + {{> api_box method_invocation_isSimulation}} {{> api_box method_invocation_unblock}} @@ -631,24 +646,141 @@ Example: {{> api_box allow}} +`allow` and its sibling function `deny` control +permission to write to the server database from the client using +auto-generated `insert`, `update` and `remove` methods. This lets +clients use the same API as the server to update the database while +maintaining the ability to enforce security against rogue clients. + +Writes initiated on the server, such as from a method, are always +allowed. The `allow` and `deny` functions only +control the client's access to the server database API. Using custom methods instead of `allow`ing direct database +access is a good approach to creating a secure app, but it is sometimes +easier and faster to develop with a simple access control function based +approach to security. + +Each access control function receives the `userId` of the currently +logged in user (or `null` if the user is not logged in) as the first +argument, and the document or documents in question as the second +argument. If the function returns `true`, it indicates that the write +should be allowed (in the case of `allow`) or denied (in the case of +`deny`). If the function returns false it does not allow or deny the +request. If no other `allow` statement allows the request, it is denied. + +
{{#dtdd "insert(userId, doc)"}} -blah blah +`doc` is the document the client wants to insert. {{/dtdd}} {{#dtdd "update(userId, docs, fields, modifier)"}} -blah +`docs` is an array of documents that the client is asking to modify. These +are fetched from the database and have not yet had the request modifications +applied. If you pass the `fetch` argument to `allow` and `deny`, these +documents can contain a subset of the database fields, which can be a +performance improvement. + +`fields` is a list of top level fields in the document the client is +asking to modify. If the client asks to modify a sub-field +(eg with `{$set: {'foo.bar': true}}`) this will contain only the top level +field (eg `['foo']`). + +`modifier` is the raw mongo modifier the client sent. Many uses will not +need to inspect this parameter. {{/dtdd}} {{#dtdd "remove(userId, docs)"}} -blah +`docs` is the list of documents the client is asking to remove. As with +`update` above, the `fetch` option controls which fields are populated +in `docs`. {{/dtdd}}
+Example: XXX test me! + + // Create a collection where users can only write documents that + // they 'own' using an 'owner' field in each document. + Posts = new Meteor.Collection("posts"); + + Posts.allow({ + insert: function (userId, doc) { + // the user must be logged in, and the document must be owned by the user + return (userId && doc.owner === userId); + }, + update: function (userId, docs, fields, modifier) { + // can only change your own documents + return _.all(docs, function(doc) { + return doc.owner === userId; + }); + }, + remove: function (userId, docs) { + // can only remove your own documents + return _.all(docs, function(doc) { + return doc.owner === userId; + }); + }, + fetch: ['owner'] + }); + + Posts.deny({ + update: function (userId, docs, fields, modifier) { + // can't change owners + return _.contains(fields, 'owner'); + }, + remove: function (userId, docs) { + // can't remove locked documents + return _.any(docs, function (doc) { + return doc.locked; + }); + }, + fetch: ['locked'] + }); + + + +`allow` and `deny` can be called multiple times to allow or deny + additional actions. The access control functions are evaluated as follows: + +- If any deny function returns true, the request is denied. +- Otherwise, if any allow function returns true, the request is allowed. +- Otherwise, the request is denied. + +In other words, the results of multiple functions are logically `OR`-ed +together. The execution order of multiple functions is not specified, +and execution of validators is not guaranteed. If the system can +determine the result after executing only a subset of the validators, it +will not call the remaining ones. + +The `insecure` package controls what is allowed when you do not call +`allow` or `deny` on a collection. With the `insecure` package +installed, all writes are allowed on collections that have never had +`allow` or `deny` called. This can be useful during development, to +quickly prototype differnt schemas without worring about security. Calling +`allow({})` on a collection will disable all client writes, even when +the `insecure` package is loaded. + + + {{> api_box deny}} +This is the sibling function to `allow`, it takes +the same arguments. The only difference is that when an access control +function registered with `deny` returns true, the request is denied +instead of allowed. + +Calling `deny` only has an effect when there are also calls to `allow`. If there are no `allow` +functions registered, `deny` functions are not called, as there is no +way the request would be permitted. + +`deny` functions take precedence over `allow` +functions. If any `deny` function returns true the request is denied and +the `allow` functions are not evaluated. + +

Cursors

diff --git a/docs/client/api.js b/docs/client/api.js index a9314df0f4..a2eddf5d25 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -199,18 +199,18 @@ Template.api.method_invocation_userId = { id: "method_userId", name: "this.userId()", locus: "Anywhere", - descr: ["Returns the id of current user, or `null` if no user logged in. XXX Will remain constant."] + descr: ["Returns the id of the current user, or `null` if no user is logged in."] }; Template.api.method_invocation_setUserId = { id: "method_setUserId", name: "this.setUserId(userId)", locus: "Server", - descr: ["Set the userId for this session. This affects all future method calls. Pass `null` to log the user out."], + descr: ["Set a user id for this session."], args: [ {name: "userId", type: "String", - descr: "XXX"} + descr: "The id of the user for this connection, or `null` to log the user out."} ] }; @@ -458,11 +458,14 @@ Template.api.allow = { id: "allow", name: "collection.allow(options)", locus: "Server", - descr: ["Control access to XXX"], + descr: ["Specify access control functions to allow clients to write to this collection using the default Mongo mutator methods."], options: [ - {name: "insert", + {name: "insert, update, remove", type: "Function", - descr: "XXX"} + descr: "Access control functions that are called for each client-initiated database write. Return true to allow the write. See below for details on the arguments to access control functions. You can specify any combination of the three functions."}, + {name: "fetch", + type: "Array of Strings", + descr: "Specific fields to retrieve when reading documents from the database to pass to `update` and `remove` access control functions. If this is not specified, all fields are retrieved. Specifying which fields to fetch can be a performance improvement."} ] }; @@ -470,11 +473,14 @@ Template.api.deny = { id: "deny", name: "collection.deny(options)", locus: "Server", - descr: ["Control access to XXX"], + descr: ["Specify access control functions to forbid clients from writing to this collection using the default Mongo mutator methods."], options: [ - {name: "insert", + {name: "insert, update, remove", type: "Function", - descr: "XXX"} + descr: "Access control functions that are called for each client-initiated database write. Return true to deny the write. See `allow` for details."}, + {name: "fetch", + type: "Array of Strings", + descr: "Specific fields to retrieve when reading documents from the database to pass to `update` and `remove` access control functions. If this is not specified, all fields are retrieved. Specifying which fields to fetch can be a performance improvement."} ] }; diff --git a/docs/client/docs.css b/docs/client/docs.css index 3905086782..b4661d1730 100644 --- a/docs/client/docs.css +++ b/docs/client/docs.css @@ -265,6 +265,10 @@ dl.callbacks dt .name, dl.methods dt .name { font-size: 1.1em; } +dl.callbacks { + margin-left: 1.5em; +} + #main dd p { margin-top: 0.5em; }