I'm currently tackling access control for my loopback3 (3.26.0) api and struggle to restict access to a property for everyone except the owner.
Lets say i have a User -> Athlete relation. With Athlete having a "secretProperty".
I set secretProperty to protected and i have a
Athlete.afterRemote('**', function(ctx, modelInstance, next) { // i am restricting property access here, which works for direct .find()}
As described here
But i still have the problem, that when i query
GET /user/{id}/athlete
the returned data contains my secretProperty and my hook is never called
How do i limit access to a property across all endpoints? (basically set it to hidden but not for certain roles/users)
Here are my model definitions:
athlete.json
{
"name": "athlete",
"plural": "athletes",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": false,
"default": ""
},
"mySecretProperty": {
"type": "number",
"required": false,
"default": 0
}
},
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": ""
}
}
}
user.json
{
"name": "user",
"plural": "users",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"validations": [],
"relations": {
"athlete": {
"type": "hasOne",
"model": "athlete",
"foreignKey": ""
}
}
}
Try adding a property to the Athlete model: "protected": ["mySecretProperty"],
and you have a set relationship between models, but not set on which fields it should run, so will not work.
https://loopback.io/doc/en/lb3/Model-definition-JSON-file.html#protected-properties
Loopback does not offer property-level security, only model-level security. To offer property-level security we did the following:
Mark the property as protected
Create a custom end-point where users could update the protected property
Restricted access to the custom end-point (2) to the users or roles who could access the property.
Another option is to use the "before save" hook, validate the user and the properties to be updated.
Allt his to say that it's a manual task you have to code yourself as it is not built-into loopback
Related
I'm trying to do a POST to the Strapi API and can't seem to figure out how to attach a 'has and belongs to many' (many to many) relationship.
I've already tried the following body's:
events: ["ID", "ID"]
name: "name"
&
events: [ID, ID]
name: "name"
Which regarding the docs should be right, I think.
There's no error, I get a '200 OK' response. It adds the record but without the relations.
Event.settings.json:
{
"connection": "default",
"collectionName": "events",
"info": {
"name": "event",
"description": ""
},
"options": {
"increments": true,
"timestamps": [
"created_at",
"updated_at"
],
"comment": ""
},
"attributes": {
"name": {
"type": "string"
},
"artists": {
"collection": "artist",
"via": "events",
"dominant": true
}
}
}
Artist.settings.json:
{
"connection": "default",
"collectionName": "artists",
"info": {
"name": "artist",
"description": ""
},
"options": {
"increments": true,
"timestamps": [
"created_at",
"updated_at"
],
"comment": ""
},
"attributes": {
"name": {
"required": true,
"type": "string"
},
"events": {
"collection": "event",
"via": "artists"
}
}
}
I'm using the standard SQLite database, strapi version 3.0.0-beta.13 and tried the request through Postman, HTML & curl.
I would love to know how to attach the relation on POST
Update 23-07:
Did a fresh install of Strapi and now everything is working.
I think it's because your set you ID as a String instead of an Integer
{
events: [1, 2],
name: "My name"
}
And here 1 and 2 are the IDs of events you want to add.
Late reply. Hoping this might help someone!
Right now I am using Strapi v4.3.2 and was facing the same issue. I overcame this by overriding the default core controller for create as explained in official docs. Relations are now visible!
async create(ctx) {
const { data } = ctx.request.body;
const response = await strapi.entityService.create(
"api::collection.collection",
{
data: data,
}
);
return {response}
}
This is (still? again?) a bug in Strapi, see: https://github.com/strapi/strapi/issues/12238
As a workaround you need to add the find-permission to the user / role who is performing the request for the related content type (you want to check first if this is a security issue for your scenario or not - alternatively you might want to try Paratron's approach which is described in the comments).
I'm starting to study loopback.
I created my app, and below this model:
{
"name": "movimenti",
"plural": "movimenti",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"mov_id": {
"type": "number",
"required": true
},
"mov_tipo": {
"type": "string",
"required": true
},
"mov_valore": {
"type": "number",
"required": true
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
I connected the model to my MySQL DB:
"movimenti": {
"dataSource": "banca",
"public": true
}
I launched the application, and went to the address indicated.
I questioned the GET method, having this error:
"stack": "Error: ER_BAD_FIELD_ERROR: Unknown column 'id' in 'field list'\n
but I do not have an ID field in my table.
How can I fix this problem?
Loopback will automatically add an id column if none of the properties of a model is mentioned as id.
Assuming for your model, property mov_id is the id. Define so in the model by adding id: true line: Reference
{
...
"properties": {
"mov_id": {
"type": "number",
"required": true,
"id":true
},
...
}
I'm using the loopback framework to create a RESTful API for my application.
Following the documentation, I create my own Customer Model extending the built-in model User.
What I'm trying to achieve is:
How can I rename and remove some properties from this built-in model?
{
"name": "Cliente",
"plural": "Clientes",
"base": "User",
"idInjection": false,
"strict":"true",
...
}
{
"name": "User",
"properties": {
"realm": {
"type": "string"
},
"username": {
"type": "string"
},
"password": {
"type": "string",
"required": true
},
"email": {
"type": "string",
"required": true
},
"emailVerified": "boolean",
"verificationToken": "string"
},
...
}
I reached the results modyfing the loopbacks models inside the node modules, but this solution does not seem the right way, is there a way to config this in my code instead change loopback base models?
I think what you are trying to do is "rename" a property, am I correct?
If so, you can do the following:
"senha": {
"type": "string",
"id": true,
"required": true,
"index": true,
"postgresql": {
"columnName": "password"
}
}
Notice that I have a "postgresql" attribute, which depends on your database connector. Check it here. Inside that attribute I have a "columnName", which is the real name of that column in my database. So "senha" is the new name of that attribute.
For hiding the username property, you could do the following in the root object:
"hidden":["username"]
Your final file should look something like this:
{
"name": "Cliente",
"plural": "Clientes",
"base": "User",
"idInjection": false,
"strict": "true",
"properties": {
"realm": {
"type": "string"
},
"username": {
"type": "string"
},
"senha": {
"type": "string",
"required": true,
"postgresql": {
"columnName": "password"
}
},
"email": {
"type": "string",
"required": true
},
"emailVerified": "boolean",
"verificationToken": "string"
},
"hidden": ["username"]
}
I have a Strongloop Loopback Node.js project with some models and relations.
The problem at hand
My problem relates how to query only those Media instances that have a relation to a certain Tag id, using the Angular SDK - while not querying Tags.media (which return Tag instances), but instead making a query somehow that returns plain Media instances.
Please read below for specific information..
Spec
Basically, I have a Model Media which has many 'tags' (model Tag). Think of a image file (Media) having various EXIF tags (Tag). Here is the relation spec (this all works as expected):
Media (media.json):
{
"name": "media",
"base": "PersistedModel",
"properties": {
"id": {
"type": "string",
"id": true
}
},
"relations": {
"tags": {
"type": "hasAndBelongsToMany",
"model": "tag"
}
}
Tag (tag.json):
{
"name": "tag",
"base": "PersistedModel",
"idInjection": true,
"properties": {
"name": {
"type": "string",
"required": true
}
},
"relations": {
"medias": {
"type": "hasAndBelongsToMany",
"model": "media"
}
},
"acls": [],
"methods": []
}
Solutions
Now, I know I could do a query like this (using Angular SDK in my example, but the syntax is the same):
injector.get('Tag').find({
'filter': {
'include': 'medias',
'where': {'id': <mytagid>}
}
});
My problem with this approach is, that I receive 1 (one) Tag instance with attached Media instances. This disrupts why whole workflow as I deal only with Media instances.. i just want to filter by Tag id, not bother about Tag at all.
Bottom line
If I see the API explorer (/explorer/), the return value of GET /api/tags/<myTagID>/medias is exactly what I need - an array of Media objects - but how to query them exactly like this using the Angular SDK (lb_services)?
I had a similar problem. One recommendation is to open the lb-services.js and try to find: /tags/:id/medias or something similar. Then you will find a comment like this: // INTERNAL. Use Tags.medias() instead. Or something similar. So that is the method that you should call. Do not call the "prototype$__get....." methods.
Then just call what it says there I suppose: Tag.medias({id:})
Other suggestions:
As you said in your description Media has many Tags. So why not use just
{
"name": "media",
"base": "PersistedModel",
"properties": {
"id": {
"type": "string",
"id": true
}
},
"relations": {
"tags": {
"type": "hasMany", <---------- hasMany
"model": "tag",
"foreignKey": "tagId" <---FK name
}
}
and
for the tags just belongsTo as type.
{
"name": "tag",
"base": "PersistedModel",
"idInjection": true,
"properties": {
"name": {
"type": "string",
"required": true
}
},
"relations": {
"medias": {
"type": "belongsTo",
"model": "media",
"foreignKey": "mediaId" <---FK name
}
},
"acls": [],
"methods": []
}
But really I don't think this is the problem because you said when you request GET /api/tags/<myTagID>/medias it returns what you want.
Then, in AngularJS you can use:
Media.tags({id:<mediaId>})
for media/:id/tags
and for the other side try:
Tag.medias({id:<tagId>})
Tag.find({
filter:{
where:{mediaId: <mediaId>} <----mediaId comes from FK name
}
})
In this case both are persistent models there is no problems, I had permission problems when doing a similar thing with data that extends User type. But that is another story...
Hope this is helpful, I changed some stuff from a similar app that I am doing and hope not making so many errors when adapting to your code...
I am trying to set up a many to many relationship in LoopBack 2.1.2
http://docs.strongloop.com/display/LB/HasManyThrough+relations
I tried to POST /api/patients/:patientId/physicians to create a new physician which links the patientId to the new physician, but does not set the appointmentDate in the appointment model.
Is there one API call to create this in one transaction?
What is the best way to add a new physician to a patient and setting the appointmentDate?
Do I have to create my own RESTFUL API call?
These are my json models
filename: appointment.json
{
"name": "appointment",
"base": "PersistedModel",
"relations": {
"patient": {
"type": "belongsTo",
"model": "patient"
},
"physician": {
"type": "belongsTo",
"model": "physician"
}
},
"properties": {
"appointmentDate": {
"type": "string"
}
},
"validations": [],
"acls": [],
"methods": []
}
filename: patient.json
{
"name": "patient",
"base": "PersistedModel",
"relations": {
"physicians": {
"type": "hasMany",
"model": "physician",
"through": "appointment"
}
},
"properties": {
"name": {
"type": "string"
}
},
"validations": [],
"acls": [],
"methods": []
}
filename: physician.json
{
"name": "physician",
"base": "PersistedModel",
"relations": {
"patients": {
"type": "hasMany",
"model": "patient",
"through": "appointment"
}
},
"properties": {
"name": {
"type": "string"
}
},
"validations": [],
"acls": [],
"methods": []
}
Disclaimer: I am a LoopBack developer working for StrongLoop.
Is there one API call to create this in one transaction?
No, there is no such API at the moment.
What is the best way to add a new physician to a patient and setting the appointmentDate?
You have to send two requests: The first one to create a physician (POST /physicians), the second one to create the appointment (POST /appointments).
Alternatively, you can use "Patient hasMany appointments" instead of "Patient hasMany physicians through Appointment", in which case the appointment can be added via
POST /patients/:patientId/appointments`
You will still have to create the physician first.
Do I have to create my own RESTFUL API call?
You can certainly do that, although I personally don't understand why two requests are a problem in this case. The operation "create a new physician with an appointment for the given patient" looks weird to me. Two steps ("create a new physician", and some time later "make an appointment") make more sense to me.
However, if you have a good example where it makes sense to create both records in one request, then please open a github issue in strongloop/loopback to discuss this further.
More info
At the moment, the "hasMany through" relation is tuned for the purpose of "hasAndBelongsToMany" relation, where the "through" model is just a container for the two id properties (foreign keys). That's the reason why the relation methods like POST /api/patients/:patientId/physicians do not support "through" properties like "appointmentDate".
I have created a github issue loopback#466 to discuss how to improve this part of LoopBack, feel free to comment there.
There is also a bug in loopback-explorer (#39), where the UI suggest that POST /patients/{id}/physicians is expecting an Appointment, even though the implementation expects a Physician instead.