MedBook Collaborations
medbook:collaborations makes it easy to write collaboration security into Meteor methods and publications. Remember: a collaboration has access to items up the collaboration tree but not down.
Diagrams
A possible collaboration scenario

... and the resulting access...

The User object
To fetch a MedBook user object, use MedBook.findUser(). To throw an error if the user is not logged in, use MedBook.ensureUser(). You should use ensureUser unless you need to do something if no user is logged in.
While it is technically possible to attach a transform directly to the Meteor.users collection (Meteor.users._transform = ...), this approach is not recommended. See here for more info.
1// outside publish functions 2let user = MedBook.ensureUser(Meteor.userId()); 3// will not reach here if no user logged in 4 5// inside publish functions 6let user = MedBook.ensureUser(this.userId); 7 8// if specific behavior is required if no user is logged in 9let user = MedBook.findUser(Meteor.userId()); 10if (user) { 11 // do something 12} else { 13 // do something else 14}
To get the personal collaboration associated with a user, use user.personalCollaboration.
1let user = MedBook.ensureUser(Meteor.userId()); 2let userCollab = user.personalCollaboration(); // "user:username@domain.suffix"
To get a list of the collaborations a user is a part of, use getCollaborations. On both client and server, this returns user.collaborations.memberOf, however on the server this list is updated before it is returned. (Internally, collaboration.getAssociatedCollaborators() is used.) Currently, the memberOf list is updated every time this function is called. In the future, we may add throttling to this update function (ex. updating only a minute after the last update).
1let user = MedBook.ensureUser(Meteor.userId()); 2let collaborations = user.getCollaborations(); 3// ex. ["user:test@test.com", "Testing lab UCSC", "Cool RNA-Seq project"]
To check if a user has access to an object, use hasAccess (returns boolean). To throw an error if the user doesn't have access, use ensureAccess. These functions take one parameter: either a collaboration object or a collaboration name (string). Use ensureAccess unless you need to do something if the user doesn't have access.
In determining if a user has access to an object, two fields are checked. A user is considered to have access if the user_id field matches the _id of the currently logged in user. A user is also considered to have access if they have access to one or more of the collaborations in the collaborations field.
1Meteor.methods({ 2 // do something, but only if they have access to the "CKCC" collaboration 3 CKCCDoSomething: function () { 4 let user = MedBook.ensureUser(Meteor.userId()); // can throw "user-not-found" 5 6 if (user.hasAccess("CKCC")) { 7 console.log("We are doing something with the CKCC collaboration!"); 8 } else { 9 console.log("Someone tried to do something but didn't have access."); 10 } 11 }, 12 13 // remove a sample group by _id 14 removeSampleGroup: function (sampleGroupId) { 15 check(sampleGroupId, String); // can throw match error 16 17 let user = MedBook.ensureUser(Meteor.userId()); // can throw "user-not-found" 18 let sampleGroup = SampleGroups.findOne(sampleGroupId); 19 user.ensureAccess(sampleGroup); // throws "permission-denied" if no access 20 21 SampleGroups.remove(sampleGroupId); // we made it! 22 } 23}); 24 25// publish a specific study 26Meteor.publish("specificStudy", function (study_label) { 27 check(study_label, String); 28 29 let user = MedBook.ensureUser(this.userId); // can throw "user-not-found" 30 let study = Studies.findOne({ id: study_label }); 31 user.ensureAccess(study); // throws "permission-denied" if no access 32 33 return Studies.find({ id: study_label }); 34});
To check if a user is an admin for a collaboration, use isAdmin (returns boolean). To throw an error if the user isn't an admin, use ensureAdmin. Like hasAccess and ensureAccess, these functions take one parameter: either a collaboration object or a collaboration name (string).
1Meteor.methods({ 2 // removes a collaboration 3 removeCollaboration: function (collaborationName) { 4 let user = MedBook.ensureUser(Meteor.userId()); 5 user.ensureAdmin(collaborationName); // throws "permission-denied" 6 7 // The collaboration remove code in MedBook is actually slightly more 8 // complicated because we don't want users to be able to create a 9 // collaboration with the name of a collaboration that has been deleted. 10 Collaborations.remove({name: collaborationName}); 11 } 12});
The Collaboration object
Two methods are available on the server for objects fetched from the Collaborations collection (ex. Collaborations.findOne()). getAssociatedCollaborators does a downwards tree traversal, returning a list of collaborations (including personal collaborations) that have access to the source collaboration. getAssociatedCollaborations does an upwards tree traversal, returning a list of collaborations that the source collaboration has access to. Both of these functions take no parameters and return an array of collaboration name strings.
These two functions are provided for medbook:collaboration's internal API and advanced users. Use them with care!