strongloop / loopback

LoopBack makes it easy to build modern applications that require complex integrations.

Home Page:http://loopback.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Read only properties

ritch opened this issue · comments

I've heard this requested a couple of times now. Creating an issue so people can discuss details. I'd like to have something like:

{"readOnly": {"propertyName": true}}

We could also make this a setting on the property itself... The version above allows us to implement it outside the scope of properties...

commented

would be a very handy feature. Maybe in combination with dynamic, calculated properties

Also very interested in this. My use case is to lock down properties that are used to define relationships so that the relationships can't be changed. For example, an address belongs to a person and is related to it through a personId property. I don't want to allow the address to be assigned to a different user by simply changing the personId value.

commented

+1

I've implemented this as a mixin, available at https://www.npmjs.com/package/loopback-ds-readonly-mixin

Yes, this seems like a huge security issue. I feel like I'm missing something that this can't be done out of the box.

Many thanks @mrfelton. Your mixin works great for now!

+1

If the acting user has write privileges on a given entity, I don't see why wouldn't he be able to update its properties.

If the underlaying database engine had a FK on the field, then the only restricción would be to point to a valid master id. I don't agree on adding model behavior that doesn't map to a native behavior on the database engine. It breaks separation of concerns and introduces an unnecesary coupling between app and db.

Since this restricción doesn't belong to the model itself, it can only exist as a middleware or as a custom rule among the business logic, which in this case would be a remote hook.

I would very much like this.

In our usecase, all model instances are associated with a Team, and on the basis of this association we derive both access rights as well as relations. This team association is set once when the instance is created, and by spec must never change after that (not even the user who owns the instance is able to change it). The association needs to be exposed via REST, however, since we use it on the client-side to query related data.

Currently we are solving this using mixins, but this almost certainly is a clumsy solution and it would be much more convenient to simply be able to mark the property as immutable.

commented

+1 Would also like to see a readOnly property.
My use case is that we store a number of followers as a property to avoid having to compute it each time from a hasAndBelongsToMany relation wich can be intensive.
But we don't want the user to be able to change the followersCount himself.

@amenadiel an user would be able to abuse its own profile :)

When created_ator updated_at are required and set at the db level by the PostgreSQL server (before_update trigger), you may want to expose the value to your users/admins but you do not want to be required to provide these values for an insert or an update. This is a pretty common use case.

It would make sense to be able to flag these 2 fields as 'readOnly'.

+1 A readOnly property would be very useful for ids.

Whell, you can undefine some fields in model.__data.

I want to make the security more explicit and define which properties are writable. So I'd like a whitelist of writable fields instead of a blacklist of readonly fields.

Would anybody mind (maybe @mrfelton ) or find it helpful if I fork https://github.com/fullcube/loopback-ds-readonly-mixin and invert it to https://github.com/devotis/loopback-ds-writable-mixin ?

+1

+1

Will this make it in? Any response would be appreciated ;)

This feature seems likely for the planned juggler updates. @raymondfeng can probably give some insight as to whether this is on the roadmap.

I mean these are sort of available in others like mongoose/restify

ok - looks like we need this too

+1

@superkhau is there any status update for this?

Heres how I'm going about this. Feedback welcome!

Disabling the put replaceById method and only using the patchAttributes (loopback 3.0.x). Just to be safe in the before hook throw error if using put.

Model.observe( 'before save', function( ctx, next ) {
  // before create
  if ( ctx.instance && ctx.isNewInstance ) {
   return next();
  }
  // before replaceById
  if ( ctx.instance ) {
    return next( new Error( 'Put not available for Model, please use PatchAttributes' ));
  }
  // before patchAttributes
  if ( ctx.currentInstance ) {
    for ( let field in ctx.data ) {
      if ( ctx.data.hasOwnProperty( field )) {
        if ( isPropImmutable( ctx.currentInstance, field )) {
          // remove immutable field
          delete ctx.data[ field ];
        }
      }
    }
  }

  return next();
});

function isPropImmutable( instance, prop ) {
  return instance.constructor.definition.properties[ prop ].immutable;
}

then in the model, you can set the props as immutable

{
 "properties": {
    "title": {
      "type": "string",
      "required": true,
      "immutable": true
    } 
  }
}

Of course you can change immutable to readOnly if you prefer that. I haven't figure out how to implement for relations yet

@superkhau is there any status update for this?

This item is on the roadmap for LB3. As for when that'll happen, I'm not sure. It'll definitely be faster if someone here could contribute a PR.

@dancingshell our use cases sound a little different. I am interested in protecting a field from being written through the REST api even during create, but allow it to be changed from the server. I accomplished this by overriding the properties setter like so:

  Model.setter.role = function () {};

Internally, we can still set the role attribute by accessing the private variable $role.

@dancingshell you aren't taking in account a try of insertion of a new attribute. Please considere this modification :)

function areReadOnly(ctx, next) {
  var isPropImmutable = function(instance, prop) {
    var property = instance.constructor.definition.properties[prop];
    return !property || property.readOnly;
  }
  var error;

  if ((!ctx.instance && !ctx.isNewInstance) && ctx.currentInstance) {
    // Before patchAttributes
    for (var field in ctx.data) {
      if (ctx.data.hasOwnProperty(field) && isPropImmutable(ctx.currentInstance, field)) {
        // Send error if try to modify readOnly properties.
        error = getError(CONSTANTS.RESPONSE_ERROR_MESSAGE_FORBIDDEN_REQUEST, CONSTANTS.RESPONSE_ERROR_CODE_FORBIDDEN_REQUEST);
        break;
      }
    }
  }

  return next(error);
}

@superkhau based on the roadmap, when can we expect this feature on LB3?

@jdhrivas It's definitely on there, but not sure when. @kjdelisle Might have a better idea as to when in regards to LB3. As for LB4, it'll probably be there by then (estimating mid 2017).

That said, does someone here want to submit a patch? We can get it reviewed/landed much sooner if this happens.

+1

This feature should be based on ACL roles, i.e only admin can edit this property, or only team owner can rename team

I'll be working on a new mixins for this purpose (ETA: TBD) and maybe after some community feedback this could become part of the base code.

Here is the general architecture idea.

column properties

  1. First of all I think MSSQL handles this issue beautifully, I'm not reinventing the wheel, so I'll implement something similar.
  2. For any given model, each property will be granted 4 generic permissions by default to all "principals": select, create, edit, delete.
    **note: select deny === hidden:["prop"] - should I remove it? This will replicate the same functionality as the "hidden" attribute
  3. The permission check will only be trigger when calling any of the public/remote methods of a model, just like ACL does.
  4. Following the pattern of ACL a propertyLevelPermissions implemented to a model will look something like this:
{ 
"name":` "foo",
"mixins": {
"PropertyLevelPermission":{
      "enable":true
    }
},
"properties": {
"id": {
      "type": "string",
      "id":true,
},
"archivedFlag": {
      "type": "string",
      "required": true,
      "permissions":[{ 
          "type":"EDIT",
          "permission":"DENY",
          "principalId": "$everyone"
          },{ 
          "type":"EDIT",
          "permission":"ALLOW",
          "principalId": "Sys Admin"
          }
      ]
    },
}

In the example above I'm denying edit/update permissions to the property "archivedFlag" for all roles and then I'm allowing only users with the "Sys Admin" role to edit/update.

Any thoughts before I get started?

+1

@panlilu and everybody else that likes the idea by @jdhrivas , please use the
image icon in the top-right corner of his post instead of posting a +1 message.

Posting a +1 message is ambiguous because we don't know if you just like read-only properties in general or the solution posted by the last poster.

https://github.com/fullcube/loopback-ds-readonly-mixin exists as a workaround for this requirement. We're winding down feature development of loopback@3.x in favour of loopback-next. Closing.