I am exploring fine-grained access for an API that we are building. APIs are hosted on API Gateway with a lambda handler and the datastore is DynamoDB. I need to apply row-level and attribute-level restrictions on the queries, depending upon the user invoking the API.
DynamoDB supports horizontal (row) and vertical (attribute) restrictions through Policies. This aws doc covers it. Let's say I have the access controls defined in a separate DB and my lambda handler has the access definition for the current user. What I want to do is be able to use some custom attributes to define my condition. e.g.
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["${customcontext:customerId}"],
"dynamodb:Attributes": [${customcontext:ListOfVisibleAttributes}]
},
"StringEqualsIfExists": {
"dynamodb:Select": "SPECIFIC_ATTRIBUTES"
}
}
Also, can this policy be used to restrict the rows returned based on a dynamic condition? e.g. I want to filter further based on a custom list of values on sort-key.
And lastly, is it worth handling this using a Policy, instead of a Lambda with dynamic query?
You wouldn't be able to dynamically create a policy per request. Its not scalable as you can't create an unbounded number of IAM policies, not to mention the increased latency that would be introduced.
Related
Users of our app can add other users as friends. Each friends list is implemented as a map where the key is the id of the users and where the value is some data related to the user. The following map is an example of how it looks like :
{'id_1' : {displayName: 'John Doe', color: 3412445}, 'id_2' : {displayName: 'Bob Alison', color: 84655467}}
We want to add rules to make data validation on the fields in the values of the map. The displayName needs to be a string and the color needs to be a number.
In the firestore rules we can check the new added friends by doing like so :
let addedFriendsKeys = newFriendsList.diff(oldFriendsList).addedKeys()
But is there a way to retrieve the values related to that list of keys?
something like :
let newFriendsList.getAll(addedFriendsKeys).forEach((p0)=>isDataValid(p0))
There are no looping operations in Firestore security rules. You will have to enumerate the keys that you want to validate.
The following answer is not mine (Credits to
Frank van Puffelen). It's coming from this post but answers perfectly to the question :
If you're asking about doing this in server-side security rules, then you've precisely hit the nail on the head: there is no ability to loop in Firebase's server-side security rules. See the reference documentation for the operations that can be performed on a List in a document. This limits what can be accomplished in security rules, and as far as I can see none of the use-cases you mention can be implemented with just security rules.
The simplest approach I can think of is by using Cloud Functions to implement the logic. You could either have the Cloud Function inspect the documents in place in the current collection, or you can have the client write to a different collection (of "pending" documents), have the Cloud Function validate the document, and move it to the actual collection.
I am curious to understand what the best practice approach is when using the Axon Framework to validate that an email field is unique to a Set of emails for a Contact Aggregate.
Example setup
ContactCreateCommand {
identifier = '123'
name = 'ABC'
email = 'info#abc.com'
}
ContactAggregate {
ContactAggregate(ContactCreateCommand cmd) {
//1. cannot validate email
AggregateLifecycle.apply(
new ContactCreatedEvent(//fields ... );
);
}
}
From my understanding of how this might be implemented, I have identified a number of possible ways to handle this, but perhaps there are more.
1. Do nothing in the Aggregate
This approach imposes that the invoker (of the command) does a query to find Contacts by email prior to sending the command, allowing for some milliseconds where eventual consistency allows for duplication.
Drawbacks:
Any "invoker" of the command would then be required to perform this validation check as its not possible to do this check inside the Aggregate using an Axon Query Handler.
Duplication can occur, so all projections based from these events need to handle this duplication somehow
2. Validate in a separate persistence layer
This approach introduces a new persistence layer that would validate uniqueness inside the aggregate.
Inside the ContactAggregate command handler for ContactCreateCommand we can then issue a query against this persistence layer (eg. a table in postgres with a unique index on it) and we can validate the email against this database which contains all the sets
Drawbacks:
Introduces an external persistence layer (external to the microservice) to guarantee uniqueness across Contacts
Scaling should be considered in the persistence layer, hitting this with a highly scaled aggregate could prove a bottleneck
3. Use a Saga and Singleton Aggregate
This approach enhances the previous setup by introducing an Aggregate that can only have at most 1 instance (e.g. Target Identifier is always the same). This way we create a 'Singleton Aggregate' that is responsible only to encapsulate the Set of all Contact Email Addresses.
ContactEmailValidateCommand {
identifier = 'SINGLETON_ID_1'
email='info#abc.com'
customerIdentifier = '123'
}
UniqueContactEmailAggregate {
#AggregateIdentifier
private String identifier;
Set<String> email = new HashSet<>();
on(ContactEmailValidateCommand cmd) {
if (email.contains(cmd.email) == false) {
AggregateLifecycle.apply(
new ContactEmailInvalidatedEvent(//fields ... );
} else {
AggregateLifecycle.apply(
new ContactEmailValidatedEvent(//fields ... );
);
}
}
}
After we do this check, we could then re-act appropriately to the ContactEmailInvalidatedEvent or ContactEmailValidatedEvent which might invalidate the contact afterwards.
The benefit of this approach is that it keeps the persistence local to the Aggregate, which could give better scaling (as more nodes are added, more aggregates with locally managed Sets exist).
Drawbacks
Quite a lot of boiler plate to replace "create unique index"
This approach allows an 'invalid' Contact to pollute the Event Store for ever
The 'Singleton Aggregate' is complex to ensure it is a true (perhaps there is a simpler or better way)
The 'invoker' of the CreateContactCommand must check to see the outcome of the Saga
What do others do to solve this? I feel option 2 is perhaps the simplest approach, but are there other options?
What you are essentially looking for is Set Based Validation (I think here blog does a nice job explaining the concept, and how to deal with it in Axon). In short, validating some field is (or is not) contained in a set of data. When doing CQRS, this becomes a somewhat interesting concept to reason about, with several solutions out there (as you've already portrayed).
I think the best solution to this is summarized under your second option to use a dedicated persistence layer for the email addresses. You'd simply create a very concise model containing just the email addresses, which you would validate prior to issuing the ContactCreateCommand. Note that this persistence layer belongs to the Command Model, as it is used to perform business validation. You'd thus introduce an example where you not only have Aggregates in your Command Model, but also Views. And as you've rightfully noted, this View needs to be optimized for it's use case of course. Maybe introducing a cache which is created on application start up wouldn't be to bad.
To ensure this email addresses view is as up to date as possible, it's smartest to ensure it is updated in the same transaction as when the ContactCreatedEvent (which contains a new email address, I assume) is published. You can do this by having a dedicated Event Handling Component for your "Email Addresses View" which is updated through a SubscribingEventProcessor (a SEP). This would work as the SEP is invoked by the same thread publishing the event (your aggregate).
You have a couple of options when it comes to querying this model prior to sending the command. You could use a MessageDispatchInterceptor which only reacts on the ContactCreateCommand for example. Or, you introduce a Handler Enhancer which is dedicated to react ContactCreateCommand to perform this validation. Or, you introduce another command like RequestContactCreationCommand which is targeted towards a regular component. This component would handle the command, validate the model and if approved dispatches a ContactCreateCommand.
That's my two cents to the situation, hope this helps #vcetinick!
I would like to send some info to Firestore database (Firebase), preferably in key-value pairs (but not necessarily), so that it can use it to evaluate access in their rules (both when reading and writing).
However, I don't want this info to be part of the path.
For example, suppose I had some passParameters method:
DocumentReference docRef =
db.collection("cities")
.document("SF")
.passParameters("abc", 123);
Then I could access this info when writing rules, like so:
service cloud.firestore {
match /databases/{database}/documents/cities/SF/ {
allow read, write: if request.parameters.abc == 123;
}
}
Please note, the above is just an example. Real-life uses cases are more complicated. In other words, don't pay too much attention to the example itself, but answer the more generic question: Is there any way to pass info to the Security Rules which is not part of the path?
You can send such parameters using custom tokens. Include those values as claims in the custom token, and use that token in your client when sending request to firestore (or signin).
This link explains how to-
1) create custom tokens, 2) include custom claims in those tokens, and 3) access those claims in the security rules.
You can have a cloud function to generate that custom token with custom claims for a specific user.
If the information you want to pass to firebase as parameter changes frequently, then this is going to be a cloud function call everytime you want to change the parameter value you are passing- so a bit costly. But if parameter tend to change less frequently (like- some role or special privilege that the user have), then this solution should work perfect and that's one of the primary benefits of custom token.
Even though it is not as simple as your example expectation snippet, still this I believe is one way to achieve what you want.
That's not supported. It wouldn't be a very "secure" security rule if the client could just specify whatever security parameters it wants with a query. That's really no different than allowing a client to pass a plaintext password that gives someone access to something. I would expect that sort of information to be discovered by an attacker.
I'd like to add a property to a Firebase user object. The user documentation says that I can only store additional properties using the Firebase real time database.
I am unsure on how this can works in practice.
What does the following mean in practice?
You cannot add other properties to the Firebase User object directly;
instead, you can store the additional properties in your Firebase
Realtime Database.
I interpret it as following:
"you cannot modify properties of a FIRUser object but you can combine this with additional objects"
I found the set function documentation which I interpet in this way:
var userRef = ref.child("users");
userRef.set({
newfield: "value"
});
Is this a sensible approach?
You're almost there. In the legacy Firebase documentation, we had a section on storing such additional user data.
The key is to store the additional information under the user's uid:
let newUser = [
"provider": authData.provider,
"displayName": authData.providerData["displayName"] as? NSString as? String
]
// Create a child path with a key set to the uid underneath the "users" node
// This creates a URL path like the following:
// - https://<YOUR-FIREBASE-APP>.firebaseio.com/users/<uid>
ref.childByAppendingPath("users")
.childByAppendingPath(authData.uid).setValue(newUser)
I've added a note that we should add this information in the new documentation too. We just need to find a good spot for it.
According to the Custom Claims documentation,
The Firebase Admin SDK supports defining custom attributes on user accounts. [...] User roles can be defined for the following common cases:
Add an additional identifier on a user. For example, a Firebase user could map to a different UID in another system.
[...] Custom claims payload must not exceed 1000 bytes.
However, do this only for authentication-related user data, not for general profile information, per the Best Practices:
Custom claims are only used to provide access control. They are not designed to store additional data (such as profile and other custom data). While this may seem like a convenient mechanism to do so, it is strongly discouraged as these claims are stored in the ID token and could cause performance issues because all authenticated requests always contain a Firebase ID token corresponding to the signed in user.
Use custom claims to store data for controlling user access only. All other data should be stored separately via the real-time database or other server side storage.
We'd like to use Firepad in our (mostly non-Firebase hosted) project, but we're having some troubles figuring out the best way to approach the problem.
Basically, we have many users, and each user can be a member of many groups. These "groups" each have their own Firepad which users can edit. We already have a deeply developed database structure using MySQL and don't really want to migrate our user data into Firebase right now, so we figured we'd get more creative.
We don't want users being able to edit the Firepads of groups they do not belong to. As such, as part of our authentication token, we figured we'd try sending along the user ID and the list of groups they belong to. Then, using the Firebase JSON security system, we could verify that the Firepad currently being edited is in the list of groups the user belongs to.
The problem is, the JSON system doesn't seem to accept many commands. There's no indexOf, and I can't call hasChild on the auth variable.
How can we ensure that users can only edit the Firepads of groups they belong to, without migrating all of our data to Firebase? (Or maintaining two copies of the database - one on MySQL and one on Firebase)
The trick here is to use an object instead of an array to store the groups (a tad awkward, I know. We'll try to make this easier / more intuitive). So in your auth token, you'd store something like:
{ userid: 'blah', groups: { 'group1': true, 'group2': true, ... } }
And then in your security rules you could have something like:
{
...
"$group": {
".read": "auth.groups[$group] == true",
".write": "auth.groups[$group] == true"
}
}
And then a user will have read/write access to /groups/<group> only if <group> is in their auth token.