I am in the process of designing a new API and am trying to make it as simple as possible. For the intended system most API consumers will be referencing objects that belong to them alone however a few other accounts will "own" objects in other peoples accounts. The questions is whether account becomes a required part of the path or an optional inclusion for these special super-accounts.
Here is an example spec and works fine in the single user context (Account ID "basic1"):
# return person "s4t4s2" for account "basic1"
GET /v1/person/s4t4s2
# return team "g3a35a" for account "basic1"
GET /v1/team/g3a35a
For super-accounts they have their own objects where the above implementation works, however they also require access to the properties of accounts they effectively own (Account ID "super1"):
# return person "s4t4s2" for account "super1"
GET /v1/person/s4t4s2
# get team "g399a2" for account "super1"
GET /v1/team/g399a2
# return person "s4t4s2" for account "basic1"
GET /v1/accounts/basic1/person/s4t4s2
Because most of my consumers will be dealing with the single account view is it best practice to use the second format for all accounts or is it entirely valid to use both formats with automatic scoping via authentication credientials when the account is omitted?
If I understand correctly, those are the same "person" resources, but they have multiple URIs? I would probably prefer having one URI for a single instance of a resource. Even if it has different "views". Different views can still be solved by either having different media-types, or just filling out fields differently on the same media-type depending on the user's permissions.
The advantage of having a single URI for a single instance is that it can be bookmarked, cached, etc. For example if a user's team/account view changes, it can't reuse its links to persons because the URIs change. I think that is not a good design.
If my understanding is wrong, and /v1/accounts/basic1/person/s4t4s2 is not the same person as /v1/person/s4t4s2 then disregard my comment. :)
Related
I want to understand how to deal with authorization in REST API with endpoints like below
GET /resource/:id
DELETE /resource/:id
GET /resource
Assumptions
User Bob is authenticated.
Bob only owns resources with id 1,2,4,5,6 but 3
System has an Access Control and Busines Logic Layers
The business layer doesn't have any awareness of data ownership
Access Control Layer has policies for resources and users and it can check if users have the right to access resources and
reject the request with HTTP403 or pass it to the Business Logic
layer to be processed
Scenario
Bob sends a request to GET /resource/2, the application returns resource details with HTTP 200
Bob sends a request to DELETE /resource/3, and the application returns HTTP 403.
Bob sends a request to list resources GET /resources?page=1&pageSize=10, the application returns resource summaries of 1,2,4,5,6 but 3
Problem
The Access control layer can prevent(403) access to a specific resource by checking the claims of the user for a given resource with defined policies.
But It should not be PREVENTED(403) when accessing search endpoints. It should be FILTERED.
Approach 1
It may assumed that search would include summaries of resources that is not owned by the authenticated user.
Approach 2
Search endpoints may be entirely separated and have the awareness of resource ownership and have the responsibility of resolving ownership and filtering.
Other endpoints stay clean by having only business logic.
Questions
Which alternative approach is better?
Is there any alternative?
Am I mixing concepts of data ownership, access control, and business logic?
I think you are mixing these concepts. Let's start from the very beginning:
If a particular user, in this case Bob, is authenticated and his authentication records have policy which defines access to a particular set of resources ( or defines prevention of access to particular set of resources ) then that status should be PREVENTED. Why?
Bob is PREVENTED from accessing particular resources. FILTERED means filtering data and that's something that you can do even when Bob has access to the data. When Bob receives 200 OK status with records that he wanted, internal functionalities of the API can still filter the data that will be adapted to the policy Bobs authentication records hold.
If in our database we have set of records like this: [1,2,3,4,5,6,7,8,9,10]
And we want to create policies that will prevent some users from access a particular record then we can have policies set up in the way where we describe records that define access a particular user has. On example policy can define a record that holds number 3 in a array ( [3] ) and based on this we can create a logic that would obviously filter out the data that's in the array and return [1,2,4,5,6,7,8,9,10].
However, this model heavily depends on how your data is structured. Questions you might want to ask yourself before designing your policy records:
How are my records structured? Do they have records/tables that I can split my data into? If they do, I can define something like <COLLECTION/TABLE_NAME>:<ACCESS_LEVEL> which would in this case yield numbers:* or numbers:[1,2,4,5,6,7,8,9,10].
Can I save access needed into my records? Common practice is to have needed access definitions saved in the records or related records of the data you save. Something like: access_needed: [ "read", "write" ]
Again, it all comes down to your records and based on that you can structure how to define your policy format.
I am working on an API endpoint that returns a list of products:
"api/products"
The endpoint accepts the following parameters:
page_size
page_number
Each product has a boolean property named IsApproved.
In the web application used by common users I always want to return only the Approved products ... On the web ADMIN application used by administrators I want to return all products, Approved or Not ...
My idea would be to add a new parameter (enumeration) named:
ApprovedStatus
And the values would be Approved, NotApproved and All.
On each API call I would check the user permissions ... If is admin I will consider the value on this parameter. If not then I will always return only approved products.
Another solution would be to have different endpoints ...
Any advice on which approach to take or is there other options?
The approval status is part of the product, therefore, in a perfect REST world, you don't want a different endpoint at all since you're accessing the same resource.
Then, for filtering a resource based on a property value, I think the convention is that if you specify that property as a query parameter it will only return those matching the value, and if not, it will return all of them, so I don't see the need to define a special ApprovedStatus parameter with some special values. Just query by isApproved!
Finally, about how to handle authorization. This, I think, should be handled at a completely separate layer**. If authorization is involved, you should have an explicit authorization layer that decides, for a specific resource and user, wether access is granted or not. This means the query would be triggered and if one of the resources generated by the query fails to be authorized for the user that triggered the query, it's taken out of the results. This accomplishes the behaviour you want without having any code that is checking specific users against specific query parameters, which is good because if tomorrow you have another endpoint that exposes this objects you won't have to implement the same authorization policy twice. Pundit is a perfect example on how to do this with Ruby elegantly.
**Of course, this approach retrieves data from the database unnecessarily which could matter to you, and also opens your endpoint up to timing attacks. Even then, I would consider tackling these problems premature optimizations and should be ignored unless you have a very good reason.
You're right about your ideas:
You can create a new endpoint just for admins, that will return all products
You can use a kind of authorization (e.g. Authorization Header) in order to check if the API is being called through admin or normal user. Then you can route internally to get all products or just IsApproved products.
You can add a proxy in front of your API to route to the right action, but it can also be achieved directly in the API but I think the second solution is easier.
Adding one more property is a bad idea.
In my opinion, adding another end point is very good. Because it will increase the protection in the admin end point.
Otherwise, since it is a web application, Simply set a cookie and a session to identify and separate the admin and user.
Going with the principle of least astonishment, I'd be in favour of adding a second endpoint for admin users. Such that you'll have:
GET /api/products (for regular users)
GET /api/admin/products (for admins)
This allows your code and API documentation to be nicely separated, and all of the admin-specific authentication details can live under the "admin" namespace.
The intention behind each API call is also clearer this way, which helps developers; and means that you can differentiate between admin vs regular usage in any usage stats that you track.
With ApprovedStatus, I think the specifics here don't matter much, but - considering what a developer using the API might reasonably expect / assume - it would be good to:
Ensure the ApprovalStatus parameter name matches the property name for "approval" that you return with each product object
Defaults to "approved" if it is not specified
Alert the user when an invalid value is specified, or one that they don't have access to
Bottom line: to answer your headline question - I think it's bad practice to ignore user input... sometimes. Design your API such that distinctions around when input can be passed in is very clear; and always alert the user if you receive input values that are technically acceptable, but not in the way that the user has requested, or for their access level. Ignoring values that are plain wrong (e.g. an argument that doesn't exist) is another story, and can be useful for future proofing or backwards compatibility.
Let's think of a simple REST-based web service to store and retrieve JSON objects. Without any authentication, every user can access any data.
Now let's add usage of JWTs to authenticate users. Of course, since we are not doing authorization, still every user can access anything, but at least now we know who accesses what.
Next step: When storing an object, save the user's sub claim with the object, and verify that the user's sub claim matches the one of the requested object, and if so, deliver it. Now we have authorization on a per-user basis. So far, so easy.
Now let's add the possibility to let users share objects with other users. For the sake of simplicity, say, we want to have predefined groups (i.e., roles) and we want to let the user choose which group (role) has access to the objects they create. This is still easy, as you have a predefined list of groups, so you can let the user choose one or more, and attach them to the object. Additionally, the identity provider needs to be configured in a way that it put a groups claim into every user's token, so we can match them. As we can already see from the length of this paragraph, things become more complex.
Question 1: Am I right so far, that handling "static" groups this way it the way to go?
Now, let's give the users the opportunity to create groups on their own. This is still not complicated, but how do we make the identity provider use the dynamically created groups? For sure we do not want to make an administrator update the identity provider's configuration every day ;-).
Question 2: How do we handle dynamically created groups?
Now, finally, let's forget about groups, and let's say, that we want to allow the users to be able to simply share their objects with other users. This should be configurable for every object individually. How do we do this? Do we save a list of users on the object? If so, what exactly do we save? The sub claim? If so, how does the owner user know the appropriate values? Or ...?
And: Supposed the users want to put their friends dynamically into dynamically generated circles, how would we do that?
Question 3: How do tokens and dynamically created groups with dynamically assigned users work with each other?
In my opinion the tokens should only include identity-information(-claims) you'll need to identify the user on your ressource server or that wont change when refreshing, since you wouldn't want your user (or your app in place) to have to refresh his access tokens each and every time his permissions change (since access tokens grant access for a specific amount of time you wouldn't want your user to have access to certain ressources that he's lost the access rights to since his last token refresh by not refreshing the token and simply using the old one either). The more security related information you append to your token, the more vulnerability you may add to your system (depending on your token lifetime).
Stating so, I would (and always do) identify the users roles or groups by his user-id (which is included in the jwt-token) on the ressource server. To achieve this, I always attach the users identity-information to the current request on my ressource server and attach "dynamic"-claims like the users role or group to the identity.
By adding only the identity information I need to identify the user and the user's rights on my ressource server, I tend to use my identity providers across multiple applications without handling application scope on the identity provider, so I can use the exact same access token for multiple independent ressource servers.
Not sure if I'm going about this the "right" way.
In my application, I have recently had the requirement added for a second "type" of user. This means realistically I will need to route this user to a different controller than the primary type.
As a more concrete example:
The primary user of the application will be staff members. They will need to see company wide information.
Now, clients of the company will need to be able to log into the application, and see information specific to their needs (and no more).
Furthermore, there are likely to be more types of user in the future.
What is the "correct" way of designing/implementing this?
I think that, if you control the users role in the TWIG templates and showing them the only links that they could access (and, of course, protecting the routes with firewalls in the security.yml) may work.
OK, so it's a badly phrased question. But it's hard to explain in a single line.
I've tried to read the Shibboleth documentation and being a newbie got out of my depth fairly rapidly. I don't really want to spend days understanding it if an expert can take half a minute to say "no chance, that won't work".
I have many groups of users, lets say (for now) that groups are different companies.
What I'd like to do is only allow users to see some fields from other companies.
For example I'm Alice in Company A and I can see that Bob in Company B has an email address bob#b.com. He can see that I'm alice#a.com
However everyone else in Company B can see that Bob has a last name and a phone number etc.
And everyone else in Company A can see my details.
To make this more complicated, lets say that Bob and I become friends and decide we want to share our information then we create a "transient" group "alice&bob". Because we are both members of that group, we can both see each others full details. (But nobody else in A can see Bob's details unless they are also friends and vice versa)
I can sort all that out in application code by querying all attributes and relationships and only showing what's relevant but for extra security I'd like to limit the disclosure of information at source.
I think I need to use attribute filters but not sure if they are able to give me this level of control. With this flexibility of being able to form relationships, will I need to build filter files on the fly and then end up with thousands of filters that Shibboleth starts to choke on because the logic is so long.
Something like the "is requester in group" filter rule :
https://wiki.shibboleth.net/confluence/display/SHIB2/IdPFilterRequirementAttributeRequesterInEntityGroup
The answer above is quite good, but i believe that non shibboleth users will find it confusing.
The quick answer is You really don't want to do it this way, it may be possible to do but for 100% there are better tools to do it.
Ok, full version now (sorry for being too obvious i some places).
In shibboleth architecture we can distinguish two main components.
Identity Provider IdP- which holds information about users from specific organizations.
Service Provider SP - which are generally some service or protected resource, for which we can define some access rules
In your example credentials for Alice and Bob could be stored in different IdP, because they are member of different organizations/companies, or (which isn't exactly matching the whole pattern) you can have one IdP for all users, and "company" is just one of user attributes. IdP doesn't provide you any kind of api that will give you opportunity to access users attributes for any user, apart from the one that is being authenticated.
On the other hand you have your SP, which hold some super secret resources, for which you can define policies. And in which you would like to define polices for user information.
And here lays the problem, on SP side you don't have access to whole users database, that's the way Shibboleth works. You can of course treat all users information as a resource in your SP, but why in the hell would you like to use Shibboleth if you have clear access to all users credentials on you application side?
If you store all users information on you service side I believe that any well designed relational database with some kind of authentication for your service will be better than shibboleth for this job.
Hope that helped.
This is not a job for Shibboleth or for most SAML/SSO providers, for that matter. The attribute filtering you speak of is used for filtering those attributes between the IdP and SP ... which is basically saying : let service provider or "application" B see the following attributes from IdP A.
Once you transmit the attributes to the SP on the other end, Shibboleth does not (and indeed cannot) provide you with a mechanism to prevent users of application B from seeing any data that you present to them ... in fact, they really shouldnt be able to see any data transmitted by the IdP unless you are exposing it in someway via your application.