Skip to content
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

Disable all remote methods and enable only selected ones (white-list) #651

Closed
voitau opened this issue Oct 15, 2014 · 75 comments
Closed

Disable all remote methods and enable only selected ones (white-list) #651

voitau opened this issue Oct 15, 2014 · 75 comments

Comments

@voitau
Copy link
Contributor

voitau commented Oct 15, 2014

There is a bunch of existing open/closed issues with questions how to hide the method of the model. My question is about how can all the methods be disabled first and then enabled only those which are necessary? (basically, duplicates this comment: #465 (comment))

Here is the simple function which hides all the methods of the model:

/**
 * Hides all the methods besides those in 'methods'.
 *
 * @param Model model to be updated.
 * @param methods array of methods to expose, e.g.: ['find', 'updateAttributes'].
 */
var setMethodsVisibility = function(Model, methods) {
  methods = methods || [];
  Model.sharedClass.methods().forEach(function(method) {
    method.shared = methods.indexOf(method.name) > -1;
  });
};

However, it doesn't hide two groups:

  • remote methods, which have to be hidden like this: Model.disableRemoteMethod(name, isStatic)
  • custom methods, like User.login or User.resetPassword (didn't find any way to hide them at all);

Could you please advise what would be the correct way to hide everything (without specific call for each method by its name explicitly) and allow only the methods which are necessary?

@crandmck
Copy link
Contributor

@bajtos Can you please clarify which part of this is docs and which is an enhancement request.

@bajtos
Copy link
Member

bajtos commented Oct 23, 2014

@voitau Could you please advise what would be the correct way to hide everything (without specific call for each method by its name explicitly) and allow only the methods which are necessary?

AFAIK, this is not possible at the moment. Let's keep this issue open to track your request for that. Note that the implementation will require changes in the strong-remoting module too.

However, it doesn't hide custom methods, like User.login or User.resetPassword (didn't find any way to hide them at all);

You have to call User.disableRemoteMethod before creating the explorer middleware. I have filled a new GH issue to fix that - see #686.

@crandmck Can you please clarify which part of this is docs and which is an enhancement request.

After a closer look, this is a pure enhancement. I am going to remove the "doc" label.

@bajtos bajtos changed the title Hide all Model REST API methods and enable only necessary Disable all remote methods and enable only selected ones (white-list) Oct 23, 2014
@elhoyos
Copy link

elhoyos commented May 12, 2015

I'm able to hide my remote methods from the explorer when using @voitau's function in 2.17.2.

@ghost
Copy link

ghost commented Jul 23, 2015

+1

@gholias
Copy link

gholias commented Sep 16, 2015

great code @voitau, thanks!
I'm new to loopback, what is the best place to put that code so I can use for all my models?

@ghost
Copy link

ghost commented Sep 16, 2015

To hide ALL methods, I ended up doing this

function disableAllMethodsBut(model, methodsToExpose)
{
    if(model && model.sharedClass)
    {
        methodsToExpose = methodsToExpose || [];

        var modelName = model.sharedClass.name;
        var methods = model.sharedClass.methods();
        var relationMethods = [];
        var hiddenMethods = [];

        try
        {
            Object.keys(model.definition.settings.relations).forEach(function(relation)
            {
                relationMethods.push({ name: '__findById__' + relation, isStatic: false });
                relationMethods.push({ name: '__destroyById__' + relation, isStatic: false });
                relationMethods.push({ name: '__updateById__' + relation, isStatic: false });
                relationMethods.push({ name: '__exists__' + relation, isStatic: false });
                relationMethods.push({ name: '__link__' + relation, isStatic: false });
                relationMethods.push({ name: '__get__' + relation, isStatic: false });
                relationMethods.push({ name: '__create__' + relation, isStatic: false });
                relationMethods.push({ name: '__update__' + relation, isStatic: false });
                relationMethods.push({ name: '__destroy__' + relation, isStatic: false });
                relationMethods.push({ name: '__unlink__' + relation, isStatic: false });
                relationMethods.push({ name: '__count__' + relation, isStatic: false });
                relationMethods.push({ name: '__delete__' + relation, isStatic: false });
            });
        } catch(err) {}

        methods.concat(relationMethods).forEach(function(method)
        {
            var methodName = method.name;
            if(methodsToExpose.indexOf(methodName) < 0)
            {
                hiddenMethods.push(methodName);
                model.disableRemoteMethod(methodName, method.isStatic);
            }
        });

        if(hiddenMethods.length > 0)
        {
            console.log('\nRemote mehtods hidden for', modelName, ':', hiddenMethods.join(', '), '\n');
        }
    }
};

@superkhau
Copy link
Contributor

FYI, I am working on a feature that enables/disables sharedMethods using settings in config.json/model-config.json. It's both a whitelister and blacklister. See #1667

@mrbatista
Copy link

@superkhau Is there any sprint planned for these features?

@superkhau
Copy link
Contributor

@mrbatista Anything marked with a label is up for sprint planning. There is no guarantee when the issue will be pulled into the current sprint. See https://waffle.io/strongloop-internal/scrum-loopback.

@devonatdomandtom
Copy link

@EricPrieto In what file did you run that method?

@ghost
Copy link

ghost commented Dec 22, 2015

@devonatdomandtom in any file... Just pass the model that you want the methods to be hidden. I usually run that on the model implementation.

@devonatdomandtom
Copy link

@EricPrieto thanks! did you include the function definition there as well?

@faridnsh
Copy link
Contributor

This is what I'm doing to disable current methods:

  YourModel.sharedClass.methods().forEach(function(method) {
    YourModel.disableRemoteMethod(method.name, method.isStatic);
  });

@ghost
Copy link

ghost commented Dec 24, 2015

@devonatdomandtom no, I store this function in some helpers library

helpers.js

module.exports.disableAllMethods = function disableAllMethods(model, methodsToExpose)
{
    if(model && model.sharedClass)
    {
        methodsToExpose = methodsToExpose || [];

        var modelName = model.sharedClass.name;
        var methods = model.sharedClass.methods();
        var relationMethods = [];
        var hiddenMethods = [];

        try
        {
            Object.keys(model.definition.settings.relations).forEach(function(relation)
            {
                relationMethods.push({ name: '__findById__' + relation, isStatic: false });
                relationMethods.push({ name: '__destroyById__' + relation, isStatic: false });
                relationMethods.push({ name: '__updateById__' + relation, isStatic: false });
                relationMethods.push({ name: '__exists__' + relation, isStatic: false });
                relationMethods.push({ name: '__link__' + relation, isStatic: false });
                relationMethods.push({ name: '__get__' + relation, isStatic: false });
                relationMethods.push({ name: '__create__' + relation, isStatic: false });
                relationMethods.push({ name: '__update__' + relation, isStatic: false });
                relationMethods.push({ name: '__destroy__' + relation, isStatic: false });
                relationMethods.push({ name: '__unlink__' + relation, isStatic: false });
                relationMethods.push({ name: '__count__' + relation, isStatic: false });
                relationMethods.push({ name: '__delete__' + relation, isStatic: false });
            });
        } catch(err) {}

        methods.concat(relationMethods).forEach(function(method)
        {
            var methodName = method.name;
            if(methodsToExpose.indexOf(methodName) < 0)
            {
                hiddenMethods.push(methodName);
                model.disableRemoteMethod(methodName, method.isStatic);
            }
        });

        if(hiddenMethods.length > 0)
        {
            console.log('\nRemote mehtods hidden for', modelName, ':', hiddenMethods.join(', '), '\n');
        }
    }
};

models/SomeModel.js

var disableAllMethods = require('../helpers.js').disableAllMethods;

module.exports = function(SomeModel)
{
    disableAllMethods(SomeModel, [... methodsIDontWannaHide]);
    ...
};

@devonatdomandtom
Copy link

@EricPrieto Amazing sir. That was exceedingly helpful

@CarlosCuevas
Copy link

@EricPrieto thanks!

@ghost
Copy link

ghost commented Jan 4, 2016

Anytime, guys :)

@ChopperLee2011
Copy link

@alFReD-NSH thanks, ur code is simple.

@crandmck
Copy link
Contributor

crandmck commented Nov 9, 2016

@gunjpan Cool--please keep me posted. I added the needs-doc label to remind us when we get to that point. Thanks.

@dancingshell
Copy link

I have refactored the code to be a little more DRY and also include a method for passing in an array of endpoints to disable.

'use strict';
const
  relationMethodPrefixes = [
    'prototype.__findById__',
    'prototype.__destroyById__',
    'prototype.__updateById__',
    'prototype.__exists__',
    'prototype.__link__',
    'prototype.__get__',
    'prototype.__create__',
    'prototype.__update__',
    'prototype.__destroy__',
    'prototype.__unlink__',
    'prototype.__count__',
    'prototype.__delete__'
  ];

function reportDisabledMethod( model, methods ) {
  const joinedMethods = methods.join( ', ' );

  if ( methods.length ) {
    console.log( '\nRemote methods hidden for', model.sharedClass.name, ':', joinedMethods, '\n' );
  }
}

module.exports = {
  disableAllExcept( model, methodsToExpose ) {
    const
      excludedMethods = methodsToExpose || [];
    var hiddenMethods = [];

    if ( model && model.sharedClass ) {
      model.sharedClass.methods().forEach( disableMethod );
      Object.keys( model.definition.settings.relations ).forEach( disableRelatedMethods );
      reportDisabledMethod( model, hiddenMethods );
    }
    function disableRelatedMethods( relation ) {
      relationMethodPrefixes.forEach( function( prefix ) {
        var methodName = prefix + relation;

        disableMethod({ name: methodName });
      });
    }
    function disableMethod( method ) {
      var methodName = method.name;

      if ( excludedMethods.indexOf( methodName ) < 0 ) {
        model.disableRemoteMethodByName( methodName );
        hiddenMethods.push( methodName );
      }
    }
  },
  /**
   * Options for methodsToDisable:
   * create, upsert, replaceOrCreate, upsertWithWhere, exists, findById, replaceById,
   * find, findOne, updateAll, deleteById, count, updateAttributes, createChangeStream
   * -- can also specify related method using prefixes listed above
   * and the related model name ex for Account: (prototype.__updateById__followers, prototype.__create__tags)
   * @param model
   * @param methodsToDisable array
   */
  disableOnlyTheseMethods( model, methodsToDisable ) {
    methodsToDisable.forEach( function( method ) {
      model.disableRemoteMethodByName( method );
    });
    reportDisabledMethod( model, methodsToDisable );
  }
};

@Discountrobot
Copy link

Discountrobot commented Nov 17, 2016

@dancingshell could you turn this into a module, maybe even a mixin?

Also, this does not hide Model.prototype.updateAttributes
temporary solution is model.disableRemoteMethod('updateAttributes', false)
swag

@drmikecrowe
Copy link

@Discountrobot and @dancingshell --- converted to mixin:

You can expose methods in your model.json file:

{
  "mixins":{
    "DisableAllMethods":{
      "expose":[
        "find",
        "findById"
      ]
    }
  }
}

or hide specific methods:

{
  "mixins":{
    "DisableAllMethods":{
      "hide":[
        "create"
      ]
    }
  }
}

https://gist.github.com/drmikecrowe/7ec75265fda2788e1c08249ece505a44

@ebarault
Copy link
Contributor

ebarault commented Nov 23, 2016

hi,
thanks for the gist :)
As proposed in #2953
What do you think of adding an option per method to decide whether the remote method should be made totally unavailable (in code and over rest), or just over rest?

@drmikecrowe
Copy link

What I chose to do (I just updated the Gist) is to not hide any methods that had explicit ACL's. So, if you granted an allow permission, then expose that method or remote method (based on these comments #651 (comment))

@gunjpan gunjpan removed their assignment Nov 23, 2016
@ebarault
Copy link
Contributor

ebarault commented Dec 1, 2016

@drmikecrowe , all, please see this gist

I refactored bits and pieces to make the code more straightforward and compatible with any version of loopback including v3 where a number of function names change.

Especially, i simplified the whole <is this a static or is this a related instance method?> logic. The reason why you only get static model methods from Model.sharedClass.methods() in your code is that you don't wait for all models to be attached, this is easily fixed by waiting for app.on('started') event. The prototype. prefix appending is then decided against the isStatic property of the remote method. This way there's no need to establish beforehand a list of possible methods prefix, making it time proof.

I also added an option for the mixin to work even if not specifying any method to explicitly hide or expose

Also included is an index.js file to allow for mixin declaration

Note: the code now uses the disableMethodByName method from loopback v3, please adapt if your loopback version does not support it yet.

@ebarault
Copy link
Contributor

ebarault commented Dec 1, 2016

After all I bootstrapped a whole new mixin which is more compact (leveraging on lodash sugar):

the option to enable methods from ACLs is now configurable with enableFromACLs param (which defaults to true)

@PaddyMann
Copy link

@ebarault Where did you spot enableFromACLs ? Not seen anything mentioned of it and searching Google and the codebase returns nothing...

@ebarault
Copy link
Contributor

ebarault commented Dec 1, 2016

@PaddyMann : it's from my own mixin code : here
you configure it in the mixin setup section of the Model definition : here

(although it's intended to bet set when expected false, as it defaults to true)

@PaddyMann
Copy link

ah great stuff (sorry I misread your previous comment!)

I'll probably give your mixin a try in the next week or two :)

@c3s4r
Copy link

c3s4r commented Dec 1, 2016

@ebarault: what would be the differences between using your gist and this module https://github.com/Neil-UWA/loopback-remote-routing ? (Are there any differences?) If there are differences, I would recommend creating your code as a npm module.

@PaddyMann
Copy link

@c3s4r You meant your comment for someone else? (no gists from me?)

@c3s4r
Copy link

c3s4r commented Dec 1, 2016

Sorry, I meant @ebarault. I just edited my comment.

@ebarault
Copy link
Contributor

ebarault commented Dec 1, 2016

@c3s4r thanks for bringing this mixin through
essentially the difference is that the solution proposed in this discussion can automatically enable only the methods explicitly covered by static ACLs in the model configuration, so you don't have to enumerate them manually once more

yes, the idea is to port it as an npm module ultimately

@c3s4r
Copy link

c3s4r commented Dec 2, 2016

I just finished creating a module for this. I changed the names of the options a little bit, and added unit tests. I manually tested on loopback 2 and 3, and it seems to be working fine.

https://www.npmjs.com/package/loopback-setup-remote-methods-mixin

I'm also thinking on adding an option for defining new remote methods as well. (Which will be deprecated when the Methods section is implemented for Loopback 3, but can be useful for Loopback 2).

Any comments or suggestions are welcome :)

@ebarault
Copy link
Contributor

ebarault commented Dec 5, 2016

As I said: I will be pushing the mixin as an npm module soon.

@c3s4r : although you most probably did this with all the best intentions :

  1. mention the contributors when publishing on npm, don't just publish as you own creation. I'm referring to the people who brought something from this discussion
  2. the way you refactored does not work for nonStatic methods named with prototype. that are enabled from ACLs definition

@c3s4r
Copy link

c3s4r commented Dec 5, 2016

@ebarault: Sorry, I did this on my best intentions, didn't want to steal the credit. I couldn't wait because I was needing it for a project, so I putted some effort on doing the unit tests, refactoring the code and publishing the module. Anyways,... let me know how you would like to proceed. I can mention you on the credits, or I can transfer you the project.

@ebarault
Copy link
Contributor

ebarault commented Dec 5, 2016

-> no pb @c3s4r : not my full credit at all either. We can add a list of contributors from this thread.
It remains that the implementation is broken for now. I'll reach out on your github repo to discuss these, we can co-author it if you will. Just give me a moment to move on with other pending stuff.

@c3s4r
Copy link

c3s4r commented Dec 6, 2016 via email

@drmikecrowe
Copy link

drmikecrowe commented Dec 6, 2016

@c3s4r I wouldn't mind being listed as "contributing", but I didn't do the heavy lifting (mainly started the mixin process). @ebarault really took my start and ran with it. NPM username is "drmikecrowe"

@c3s4r
Copy link

c3s4r commented Dec 6, 2016

I just pushed the package to npm with the outstanding bug fixed. Also, I added @ebarault and @drmikecrowe as contributors and a credits section in the README file.

https://www.npmjs.com/package/loopback-setup-remote-methods-mixin

@ebarault
Copy link
Contributor

ebarault commented Dec 6, 2016

hi @c3s4r,
this should be tinkered as it might lead to unexpected results since you make the choice to exclude both instance and static methods for a unique given name, let's continue the discussion on your github.

@christiaanwesterbeek
Copy link

@ebarault I think it makes sense to create an issue for that at https://github.com/devsu/loopback-setup-remote-methods-mixin/issues

@c3s4r
Copy link

c3s4r commented Dec 7, 2016

@ebarault: I'm aware of the limitation, I didn't see it like a big issue, anyways, if you think it should be changed, please open an issue on the other repo and let's continue the conversation there.

@go2smartphone
Copy link

go2smartphone commented Jun 29, 2017

thanks all for the module, it is cool, but it is slow when you have many models and each of them has many relations. anything we can do about it?

@denblackstache
Copy link

denblackstache commented Nov 2, 2017

@go2smartphone Something to deal with server.on('started', () => { ... }) ≈5 sec to process each model. Actually it works the same without looking if app is started (Loopback v2.36.0).

@bajtos
Copy link
Member

bajtos commented Dec 8, 2017

@go2smartphone @HyperC0der Please open a new issue describing the problem you are facing in detail, ideally with a small app reproducing it (see http://loopback.io/doc/en/contrib/Reporting-issues.html#bug-report).

@strongloop strongloop locked and limited conversation to collaborators Dec 8, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests