Loopback: Ability to hide model properties from Swagger and loopback-datasource-juggler independently

Created on 7 Nov 2014  路  10Comments  路  Source: strongloop/loopback

Currently, one can mark model properties as hidden from loopback-datasource-juggler independent from what is hidden from loopback-explorer/Swagger. This is evident in the source code by looking at ModelBaseClass.isHiddenProperty in loopback-datasource-juggler/lib/model.js:

// ...
var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden);
// ...

I can set hidden properties in both hiddenProperties and hidden in my model.json file, and those properties will not be sent in responses containing the model in question. If I choose to use the hidden property, those properties will also be hidden from Swagger, as seen in the isHiddenProperty function defined in loopback-explorer/lib/model-helper:

// ...
definition.settings.hidden.indexOf(propName) !== -1
// ...

What this means is that you have the ability to hide properties in responses that contain the models, but they will still show up in Swagger. This feature would be fine if it were a two way street, meaning I could hide properties from Swagger but have them exist in model-containing responses. Perhaps if the isHiddenProperty did a check on an additional, unique settings property:

// ...
definition.settings.hidden.indexOf(propName) !== -1 || definition.settings.hiddenFromExplorer.indexOf(propName) !== -1 
// ...

With such an implementation, I could hide a property completely by putting it in the hidden array, hide properties from being displayed in Swagger by putting those properties in hiddenFromExplorer, and prevent things from being returned in model-containing responses by putting those properties in the hiddenProperties array.

The best use case for this in my opinion is the ID field. All of my IDs are auto-generated, so I don't want them to display in explorer/Swagger. However, I do want the ID returned in my responses as it is necessary for many operations.

feature stale

Most helpful comment

I guess we are talking about different scopes of visibility for properties.

  1. between an api client and an api server

    • writable only (can be written, but not read) - password

    • readable only (can be read, but not write) - auto-generated ids

    • regular (default, readable & writable) - most properties

    • hidden (not readable or writable)

  2. between an api server and a database

    • transient (not persisted at all)

    • readable (such as serial id or uuid)

    • regular (read & write)

All 10 comments

Im facing the same concerns, +1.

I guess we are talking about different scopes of visibility for properties.

  1. between an api client and an api server

    • writable only (can be written, but not read) - password

    • readable only (can be read, but not write) - auto-generated ids

    • regular (default, readable & writable) - most properties

    • hidden (not readable or writable)

  2. between an api server and a database

    • transient (not persisted at all)

    • readable (such as serial id or uuid)

    • regular (read & write)

First of all, I kept on searching through docs and can't find anything related to this question. How do I define "different scopes of visibility for each properties"?

Secondly, how do I define "scope of visibility for each properties" rules for each ACL roles? I've seen example how to setup ACL for each Models, but not the Properties level. Maybe I am missing something?

Sorry for confusion. I was brainstorming the potential settings we can specify for properties. We don't have the fine-grained control for the property visibility yet.

@jaxyeh, to my knowledge, you cannot set different access permissions at the property level from the standard model.json file, whether using ACLs or something else. In order to get any semblance of control at the property level, you'll have to use validation methods or remote/model hooks - I had to implement a mix of all 3 in my current implementation. For validation that requires user permissions/ACLs, you'll have to perform those checks in the remote hooks, as that is the only place you'll have access to the request object, and therefore the currently logged-in user.

@raymondfeng - My issue is specifically about the ability to hide things from Swagger and the ability to hide things from the result output using the hidden and hiddenProperties properties in the model.json file. Currently, you can hide things from both or only result output - I would like the ability to hide things from both, only result output, and only Swagger, independently.

I do like your ideas though, and it addresses the issue @jaxyeh is having. Once implemented, it should also address the Swagger/result discrepancy. I can picture it as follows:

  1. between an api client and an api server

    • writeOnly



      • can be written, but not read


      • e.g. password


      • show in Swagger, hide in result


      • translates to regular for DB layer



    • readOnly



      • can be read, but not written


      • e.g. id, created, lastUpdated


      • hide in Swagger, show in result


      • dynamically determine the type for the DB layer, some may need to be DB technology-specific


      • how will the user specify what the intial value of this property will be? perhaps using a property called "readOnlyType"?



    • default



      • readable & writable


      • most fields will use this type, as it will be the default for non-id fields


      • show in both


      • translates to regular for DB layer



    • hidden



      • not readable or writable


      • perhaps used for system fields? these will be accessible in code


      • hide in both


      • translates to regular for DB layer, but is not exposed at all to api client, it is only accessible in code


      • perhaps ACLs could be used to make this accessibe to certain roles, like admins?



  2. between an api server and a database

    • transient



      • not sure how this is applicable, as it would never be passed to or retrieved from the DB


      • I imagine it would be recognized as transient at the server-level and would never be passed to the DB layer. What is a use case for this? Should some sort of state/session value from the request ever need to be controlled from the model.json file, but never persist?


      • How would a transient property be identified in model.json?



    • readable



      • hide in Swagger, show in result



    • regular



      • show in both



To help address @jaxyeh's issue, ACLs could also be used for property control.

Here is a sample use of these in model.json for a MongoDB implementation:

{
  "name": "product",
  "plural": "products",
  "base": "persistedModel",
  "strict": true,
  "properties": {
    "id": {
      "type": "text",
      "required": true,
      "access": "readOnly",
      "readOnlyType": "auto"
    },
    "created": {
      "type": "text",
      "required": true,
      "access": "readOnly",
      "readOnlyType": "createdTimestamp"
    },
    "lastUpdated": {
      "type": "text",
      "required": true,
      "access": "readOnly",
      "readOnlyType": "updatedTimestamp"
    },
    "requestIPAddress": {
      "type": "text",
      "required": true,
      "access": "hidden"
    },
    "name": {
      "type": "text",
      "required": true,
    },
    "type": {
      "type": "text",
      "required": true
    },
    "manufacturerId": {
      "required": true
    },
    "supplierId": {
      "required": true
    }
  },
  "hidden": [],
  "hiddenProperties": [],
  "hiddenAPIProperties": [],
  "validations": [],
  "relations": {
    "manufacturer": {
      "type": "belongsTo",
      "model": "manufacturer",
      "foreignKey": "manufacturerId"
    },
    "supplier": {
      "type": "belongsTo",
      "model": "supplier",
      "foreignKey": "supplierId"
    }
  },
  "acls": [    
    {
      "accessType": "*",
      "modelProperty": "manufacturerId"
      "permission": "DENY",
      "principalType": "ROLE",
      "principalId": "$everyone"
    },
    {
      "accessType": "*",
      "modelProperty": "supplierId"
      "permission": "DENY",
      "principalType": "ROLE",
      "principalId": "$everyone"
    },
    {
      "property": "_READ_HIDDEN_PROPERTIES_",
      "permission": "ALLOW",
      "principalType": "ROLE",
      "principalId": "admin"
    },
    {
      "property": "_WRITE_HIDDEN_PROPERTIES_",
      "permission": "ALLOW",
      "principalType": "ROLE",
      "principalId": "admin"
    },
    {
      "accessType": "*",
      "modelProperty": "manufacturerId"
      "permission": "ALLOW",
      "principalType": "ROLE",
      "principalId": "admin"
    },
    {
      "accessType": "*",
      "modelProperty": "supplierId"
      "permission": "ALLOW",
      "principalType": "ROLE",
      "principalId": "admin"
    },
    {
      "accessType": "*",
      "modelProperty": "manufacturerId"
      "permission": "ALLOW",
      "principalType": "ROLE",
      "principalId": "productAdmin"
    },
    {
      "accessType": "*",
      "modelProperty": "supplierId"
      "permission": "ALLOW",
      "principalType": "ROLE",
      "principalId": "productAdmin"
    }
  ],
  "methods": []
}

It seems that this is a good long-term solution to both my issue and @jaxyeh's issue.

@raymondfeng, what do you think?

FWIW, I prefer to specify the property type (readable, writable, transient, etc.) in the per-property configuration object as opposed to listing them in per-model arrays. The latter will be more difficult to implement for mixins.

@bajtos @raymondfeng I think we should re-open this thread to address the feature requirement.

@seanxlliu with LoopBack 4.0 GA publicly available, LoopBack 3 has moved into Active LTS mode and does not accept any new features (see our Long Term Support policy).

Feel free to open a new issue in loopback-next to discuss this feature requirement for LoopBack 4 and beyond.

Make sense, thanks!

Was this page helpful?
0 / 5 - 0 ratings