In the screenshot below, my details branch/node contains lots of details named with a random-id. As shown in the example, 8641260c-900... is a detail record and there will be several others like these.
I would like to know whether my .write rule is correct or not? I wanted to enable restriction so that current auth.id exactly match the existing record's user field.
I would also wanted to restrict deletion of the record (via .remove).
Can I simply add && !data.exists() || newData.exists() to the .write rule?
Thanks in advance.
I would like to know whether my .write rule is correct or not?
First of all, giving ".read":true and ".write":true to the root will override all child node rules to true. Therefore any rules specified to child nodes will become redundant.
I wanted to enable restriction so that current auth.id exactly match the existing record's user field.
{"rules":{
"existing_record":{
"user":{
".write":"newData.isString() && auth.uid == newData.val()"
}
}
}}
I would also wanted to restrict deletion of the record (via .remove).
newData.exists() will prevent deletion of a node. newData represents how data will look like after the operation took place. Therefore, by ensuring that newData exists after the operation, deletion of a node is prohibited.
Related
I am trying to understand more in depth the difference between filter and item access control.
Basically I understand that Item access control is, sort of, higher order check and will run before the GraphQL filter.
My question is, if I am doing a filter on a specific field while updating, for instance a groupID or something like this, do I need to do the same check in Item Access Control?
This will cause an extra database query that will be part of the filter.
Any thoughts on that?
The TL;DR answer...
if I am doing a filter on a specific field [..] do I need to do the same check in Item Access Control?
No, you only need to apply the restriction in one place or the other.
Generally speaking, if you can describe the restriction using filter access control (ie. as a graphQL-style filter, with the args provided) then that's the best place to do it. But, if your access control needs to behave differently based on values in the current item or the specific changes being made, item access control may be required.
Background
Access control in Keystone can be a little hard to get your head around but it's actually very powerful and the design has good reasons behind it. Let me attempt to clarify:
Filter access control is applied by adding conditions to the queries run against the database.
Imagine a content system with lists for users and posts. Users can author a post but some posts are also editable by everyone. The Post list config might have something like this:
// ..
access: {
filter: {
update: () => ({ isEditable: { equals: true } }),
}
},
// ..
What that's effectively doing is adding a condition to all update queries run for this list. So if you update a post like this:
mutation {
updatePost(where: { id: "123"}, data: { title: "Best Pizza" }) {
id name
}
}
The SQL that runs might look like this:
update "Post"
set title = 'Best Pizza'
where id = 234 and "isEditable" = true;
Note the isEditable condition that's automatically added by the update filter. This is pretty powerful in some ways but also has its limits – filter access control functions can only return GraphQL-style filters which prevents them from operating on things like virtual fields, which can't be filtered on (as they don't exist in the database). They also can't apply different filters depending on the item's current values or the specific updates being performed.
Filter access control functions can access the current session, so can do things like this:
filter: {
// If the current user is an admin don't apply the usual filter for editability
update: (session) => {
return session.isAdmin ? {} : { isEditable: { equals: true } };
},
}
But you couldn't do something like this, referencing the current item data:
filter: {
// ⚠️ this is broken; filter access control functions don't receive the current item ⚠️
// The current user can update any post they authored, regardless of the isEditable flag
update: (session, item) => {
return item.author === session.itemId ? {} : { isEditable: { equals: true } };
},
}
The benefit of filter access control is it doesn't force Keystone to read an item before an operation occurs; the filter is effectively added to the operation itself. This can makes them more efficient for the DB but does limit them somewhat. Note that things like hooks may also cause an item to be read before an operation is performed so this performance difference isn't always evident.
Item access control is applied in the application layer, by evaluating the JS function supplied against the existing item and/or the new data supplied.
This makes them a lot more powerful in some respects. You can, for example, implement the previous use case, where authors are allowed to update their own posts, like this:
item: {
// The current user can update any post they authored, regardless of the isEditable flag
update: (session, item) => {
return item.author === session.itemId || item.isEditable;
},
}
Or add further restrictions based on the specific updates being made, by referencing the inputData argument.
So item access control is arguably more powerful but they can have significant performance implications – not so much for mutations which are likely to be performed in small quantities, but definitely for read operations. In fact, Keystone won't let you define item access control for read operations. If you stop and think about this, you might see why – doing so would require reading all items in the list out of the DB and running the access control function against each one, every time a list was read. As such, the items accessible can only be restricted using filter access control.
Tip: If you think you need item access control for reads, consider putting the relevant business logic in a resolveInput hook that flattens stores the relevant values as fields, then referencing those fields using filter access control.
Hope that helps
I'm following a tutorial about firestore but I don't understand firestore rules very well. I'm trying to allow anyone to be able to create in the standard
users/uid/
path but only allow updates if the requester is trying to update
users/theirUserId/
I saw this in the documentation, but it didn't seem to work for me:
allow write: if request.auth.uid == resource.data.author_id;
Can anyone explain the functionality of the above line and/or offer suggestions as to how I can achieve this?
Additionally, is there any way to specify rules for a specific piece of data within a document?
It looks like that your document doesn't contain a author_id field.
The Firebase documentation Writing Conditions for Security Rules use this example:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches the 'author_id' field
// of the document
match /users/{user} {
allow read, write: if request.auth.uid == resource.data.author_id;
}
}
}
It means that a random user will be able to read and write in the users collections only if their authentication ID equals the author_id field of a specific document.
The resource variable refers to the requested document, and resource.data is a map of all of the fields and values stored in the document. For more information on the resource variable, see the reference documentation.
For your second question, I recommend you to have a look on the documentation about resource variable (link in the quote above). It is the same logic as your author_id question.
You can split allow write in to three create, update, delete for specific cases.
In your case
allow create: if request.auth.uid != null;
allow update: if request.auth.uid == resource.data.author_id;
which says any authenticated users can create and only update their on document. and created user must have a field author_id which is their user id.
I know how to "translate" this into code:
if the Cancellation.User.Id is the same as currentUser.Id
if (cancellation.User.Id != currentUser.Id){...}
But what if my Cancellation object contains not one User but a List<User> Users?
How do I check if the Cancellation object contains a User whose Id is the same as currentUser.Id?
LINQ will solve this quite nicely.
if (cancellation.Users.Any(u => u.ID == currentUser.Id)){...}
Note that this is true if there is a matching user in the list, which is what you said in your last sentence, but seems counter to the code snippet you provided, which is checking if the ID does not match. If you want to trigger this code if none of the users match, just put a ! before the whole lot (not in the lambda).
My DIT:
dc=mucompany,dc=com
ou=moodlegroups
ou=moodleusers
ou=Students
mail=student1#mail.com
courseCertificate=Type1
courseCertificate=Type2
courseCertificate=Type3
mail=student2#mail.com
courseCertificate=Type1
courseCertificate=Type2
courseCertificate=Type3
the mail=student1#gmail.com entry has the mail as RDN and a bunch of other attributes(cn,sn...also custom attributes) and has like childrens the CourseCertificate=value entres also containing bunch of other attributes(courseCertificateRunning=TRUE,courseCertificateEnding=20120210,...)
I need to make a query that searchs for (&(sn=Brad)(courseRunning=TRUE)) that returns all the attributes of the parent entry and of the child entry that satisfy the filter...
Is this possible with one ldapsearch?
Any help it will means a lot to me, thanks in advance.
P.S. I'm using openldap 2.4, i try to do the queres using AD Studio
LDAP search filters are evaluated against each individual entry, to decide if it must be returned or not. They are not evaluated against a hierarchy of entries.
I'm developing a WCF Data Service with self tracking entities and I want to prevent clients from inserting duplicated content. Whenever they POST data without providing a value for the data key, I have to execute some logic to determine whether that data is already present inside my database or not. I've written a Change interceptor like this:
[ChangeInterceptor("MyEntity")]
public void OnChangeEntity(MyEntity item, UpdateOperations operations){
if (operations == UpdateOperations.Add)
{
// Here I search the database to see if a matching record exists.
// If a record is found, I'd like to use its ID and basically change an insertion
// into an update.
item.EntityID = existingEntityID;
item.MarkAsModified();
}
}
However, this is not working. The existingEntityID is ignored and, as a result, the record is always inserted, never updated. Is it even possible to do? Thanks in advance.
Hooray! I managed to do it.
item.EntityID = existingEntityID;
this.CurrentDataSource.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
I had to change the object state elsewhere, ie. by calling .ChangeObjectState of the ObjectStateManager, which is a property of the underlying EntityContext. I was mislead by the .MarkAsModified() method which, at this point, I'm not sure what it does.