0
votes

Let's say I have three models in Loopback: Reader, Book and Note. Reader is an instance of User and can log in. Relations are like that:

  • Reader has many Books
  • Reader has many Notes
  • Book has many Notes
  • Note belongs to Reader
  • Note belongs to Book

What I am trying to achieve is to query all Books of a logged-in Reader with populated Notes that belong to the Book and the Reader.

Api call would look like that:

 /api/reader/me/books?filter[include]=notes

But this returns all the notes belonging to the Book regardless if they belong to the Reader. I could add another filter to the api call but I need to filter the Notes on server side so that the reader does not have access to other readers' notes.

I've tried to add this access control to Book model:

{
  "principalType": "ROLE",
  "principalId": "$owner",
  "permission": "ALLOW",
  "property": "__get__notes"
}

and this acl to Note model

[
    {
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "DENY"
    }, {
      "principalType": "ROLE",
      "principalId": "$owner",
      "permission": "ALLOW",
      "property": "*"
    }
]

It works well for calls like this

 /api/reader/me/books/<bookId>/notes

but not for the first call with include filter. What should I do to get just reader's notes populated in the books?

Any help much appreciated.

1
I had a similar issue where I needed to restrict or allow access based on a per-instance basis instead of the per-model basis used in SL. Unfortunately I didn't find a suitable way to do this and had to implement my own authorization layer. However, one thing you could do is a simple filtering on the afterRemote hook, such that you do some sort of set subtraction to only return those instances you can access. In my case this was not possible because I needed user A's instances to be accesible by User B. This is not your case though, so it might work. - Acapulco
You can read the docs here - loopback.io/doc/en/lb2/Remote-hooks.html - It's not straight forward and probably not very efficient though - Acapulco

1 Answers

0
votes

To achieve this you will have to create a remote methods.You will have to apply filter on application level.

Suppose the user detail is Store in User model

module.exports = function(Reader){

    var async = require('async');
    Reader.fetchBooks = function(userId, callback){
        var app = this.app;
        var updatedReaderList = [];
        Reader.find({
            where: {userId: userId },
            include: {relation: "books"} 
        }, function(err, readerList){
            if(err){
                return callback(err);
            }
            if(readerList){
                if(readerList.length){
                   var series = []; 
                   readerList.forEach(function(reader){
                      var readerObj = reader.toJSON();
                      updatedReaderList.push(readerObj);
                      if(readerObj.books){
                          readerObj.books.forEach(function(book){
                              series.push(function(callback){
                                fetchNotes(app, book, readerObj.id, callback);
                              });
                          } 
                      }
                   });

                  //Now save the data in series..
                  async.series(series, function(err){
                      if(err){
                          callback(err);
                      }else{

                          //Now send the callback
                          callback(null, updatedReaderList);
                      }
                  });

                }else{
                  callback(null, [])
                }
            }else{
              callback(null, [])
            }
        });


    };

    var fetchNotes = function(app, book, readerId, callback){
        var Note = app.models.Note;
        //Fetch only those note whose reader id belongs to current reader and book belongs to same one.
        Note.find({
          where: {
            readerId: readerId,
            bookId: book.id
          }
        }, function(err, noteList){
          if(err){
            return callback(err);
          }else{
            if(noteList){
              book.notes = noteList;
            }
          }
        });
    };
}

Now you can restrict all others methods and simply allow this remote method.

{
  "principalType": "ROLE",
  "principalId": "$owner",
  "permission": "ALLOW",
  "property": "fetchBooks"
}

Note: You can use promise to remove callback hell.