We have a system built on dynamic objects - so there is a metadata table that describes these objects. For example - Organization A can have a Warehouse Object, a Client Object and a Sales Object. Organization B can have a Sales Object and a Clown Object.
Users authenticate to our rest api built on the Yii2 framework. They authenticate using a call to /user/authenticate and then they query for objects using /object/ for list / create and /object// for Read, Update, Delete.
The issue with this is: If a developer is going to integrate into the service, they would need to know all objects that have been defined in their organization including available fields they are able to read/write to. What we would like to do is provide an option to describe our data.
E.g. My initial thought would be to expose something like /object/metadata in which I would respond with a json list of resources that the user is allowed to access e.g. a Warehouse A user would see Warehouse, Client, Sale. Where a Org B user would see Sales, Clown.
I have been asked to take a look at oData as a specification for this but oData seems to define a whole convention of things (url, searching and filtering etc).
Does it make sense to implement part of the oData Spec or use it as a guide and keep our URLs the same?
Is there a part of the oData spec that would lend itself to the describing of a dynamic resource (e.g. if the user uses our system to add a column to the Sales object - the api should reflect that).
Would appreciate any thoughts on how to design / proceed with this requirement.
Thank you!
I created follow solution:
extend dektrium/yii2-rbac, where to assigments add column company
in each module (werhause, invoices,..) created roles objects
roles object has methods: canAssign(), canView(), canRemove() for user administartion
user administration panel collect all roles from all modules (scan as files) and display grouped by modules.
This solution allow to user switching between companies and for users assign different access rights to different companies and control user manager rights assigning rights
Related
The problem to face lies in the design of a RESTful API that can manage requests from multiple roles in an RBAC-based solution.
Currently we have different resources that can be accessed from different users, which can have one or more roles grouped according to their privileges.
The API we're trying to define must be as clear as possible to the client but without the overhead of adding additional metadata to the URL that could damage and even conflict with the REST practices and definitions. Therefore, we must avoid at all costs include information about the roles inside the URL. The plan is to use JWT tokens that carry in their payloads the info needed to know which permissions has the user making the request.
Having raised our current situation, let's provide an example and state the problem to solve:
Suppose we have * financiers * and * providers * as users with some roles who both want to access ** attentions ** (our resource). Should we add before the resource ** attentions ** information about the * user * whose trying to access the resource?
The endpoints in that case should be defined (as an example) as:
https://example.com/api/v1/financiers/:id/attentions
https://example.com/api/v1/providers/:id/attentions
This way we're attempting to inform the respective controllers that we want the ** attentions ** for that specific role / user which are, in some way, a sub-resource of them.
On the other hand, we could simply implement a much simpler endpoint as follows:
https://example.com/api/v1/attentions
The logic about which attentions return from the database should be now implemented in an unique method that must handle this two roles (and potentially new ones that could come up in the following features). All the information needed must be obtained from the payload from the token, exposing a much more generic API and freeing the web client from the responsibility of which endpoint call depending on the role.
I want to highlight that the attentions are managed in a Microservices Architecture and, hence, the logic to retrieve them is gathered in a single service. The cost of the API Gateway to route the two (and potentially more) of the endpoints from the first solution is a variable not to discard in our specific situation.
Having exposed our current situation:
Which we'll be the best approach to handle this issue?
Is there another alternative not contemplated that could ease the role management and provide a clean API to expose to the client?
In the second solution, is correct to return only the attentions accessible to that specific user based on the roles that it has? Isn't it counterintuitive to access an endpoint and only get some of the resources from that collection (and not all) based on its role?
I hope that someone could clarify the approach we're taking as there are little and none literature that I've found regarding this issue.
There there are multiple solutions for such kind of filtration, and developer have to select one depending on given situation.
As per my experience I can list following.
Structure
When data can't be accessed directly and developer has to use a relation (i.e a table JOIN). In that case URL have to include both the main and sub entities. Before going with this approach a good check is to ask, if the same URL can be used with POST ?
Example
If we have to fetch list of roles assigned to a specific user or want to assign additional roles then we can use
GET users/:uid/roles
POST users/:uid/roles
Security
With Multi-tenant systems where each user can have his/her private resources, i.e other users are prohibited from accessing those resources. Developer should save tenancy information and to filter the resources according to current authentication, without bothering client or requiring any additional info in URL
Example
Phone album of the user
GET photos
POST photos
Search
If it is not security or structure related but client still want to filter the result set depending on his scenario. then developer should use query-string for the filtration.
Example
Client have to fetch messages from his/her inbox or outbox or want messages which are not yet read. or he/she want to search his/her inbox
GET messages?folder=inbox
GET messages?folder=inbox&status=unread
GET messages?search=nasir
For example: We have a User model, which in turn has several Company entities. I see 2 solutions:
1) Classical. Make an API like:
/users/
/users/3/
/users/3/companies/
and issue /users or companies request separately. However, if we need to have both user and his/her companies information in one view (on Angular2) - we need to send 2 requests to the server.
2) Put the related/nested data inside the level-1 object model. In request:
/users/3/
the server will provide information about the User, together with his Companies. In this case we get all information for 1 request. But again, the company has an unlimited number of Storage entities. What if they are required in one separate view?
I'm more inclined to the first option, but I'm confused by the description of the REST style: "The view must fully represent the resource." Satisfaction of this requirement can lead to a partition of the resource into child resources and, accordingly, to smaller representations. "
Please, help with advice, I doubt in connection with the lack of experience what decision will be correct in this case. Oh yes, I forgot, Backend on Django (Python) using Django-Rest-Framework - All this is a SaaS for ~ 1000 users.
Approach 1 is an an ideal approach for REST. But when it comes to designing APIs for the displaying information on the UI it involves much more than just partitioning the APIs as per resources.
So I would suggest including the Company information in the User API. But as you suggested Company object can have very large list of Storage objects, in this case I would recommend including only the necessary and sufficient fields of Company model into User API. So that you will be able to render one view. And then later when user expands the Company section then you can pull the left-over fields from /company/<id> API.
This way you will have lesser API calls for the hits where user doesn't look for Company details and your API will be light weight as well.
For a simple application, there are 2 restfull apis managing users' information.
For example, api A is defined as /users which return a list of users. api B is defined as /user/{id} which return a user identified by his id.
However, the front page using the api A is a summary page displaying a table of a users which only need a few properties of a user, like name, age, gender, etc.. When refer a details of a user's information, there are much more properties are needed, like social security number, bank accounts, and there properties are not stored in the database but in some other system.
So, should I using different apis for there these two scenario, like /userSummaries and /userDetailes/{id} and just using apis I mentioned above?
Any advice will be appreciated.
You should have an api for every bounded context.
That being said, you should somehow map the write and the read models to the api endpoints. Aggregates commands should be mapped to put/post/patch/delete endpoints. Read-models queries should match the get api endpoints.
UPDATE:
So, should I using different apis for there these two scenario, like
/userSummaries and /userDetailes/{id} and just using apis I mentioned
above?
Yes. You should have an API end point to each Read-model.
See https://vimeo.com/41763224 and https://yow.eventer.com/events/1004/talks/1047
Yes, I would separate the two APIs. In the API layer, you have different models for the response(eg. UserSummaryModel, UserProfileModel). These models will act as view models or DTOs(data transfer objects). They are anemic classes that contains only getters and setters. The rich domain which is in a separate project will contain the User(and other domain models) business logic, constraints, validations, etc.
So in the API layer, you have to map the view models to the domain model. You can manually do the mapping or use Automapper.
#Constantin GALBENU is right what you should have an API for each context but this also depends on your application because there could be cases where you need an API to fetch data from two contexts.
I just want to add that you should not confuse the Domain of your application with the Domain of your API(s). I.E. you can have a generic API that is able to standardly hook to any of your BoundedContexts and expose their functionality either via concrete configuration or some automatized method.
This not however mean that your API code is part of you App's Domain since the API is only concerned with answering requests/logging/etc. - all things specific to an API.
Given the following scenario:
a RESTful API
that RESTful API has permissions/authorizations that can be granted to entire entity collections, and/or to particular entities, i.e. complex role based permission rules.
The API is (according to RESTful principles) HATEOS-driven (resource url's are revealed through the API. Once you login and get back the "user" resource, you are able to drive the entire API through links given in responses.
A front-end client (web app) that needs to use this API
Imagine now the front-end wants to build a menu. That menu is based on access to particular entities and/or entity collections. For example an "Administration" menu will be shown if the user has access to one of a number of different entities and/or collections.
How do I build the menu? I need to know the permissions the user has in order to build the menu propertly. I don't know all the permissions the user has because it would require walking the url's of the REST API to see all the objects the user has permissions on.
This seems like a tough thing to overcome, but maybe I am missing some obvious technique. How can this impedance mismatch overcome?
Your REST API can expose a resource (or it can be a part of the user resource returned after a user has been successfully signed in) which will contain information what resources the user can access and therefore what menu items should be available for this user.
Let's say I'm creating a RESTful service to handle orders for my warehouse over the web.
I want to allow customers to create accounts
I want a customer admin to be able to create accounts for other users in their office
I want to allow customer users to create orders
I want a site administrator to be able to create and manage all customer accounts
I want a site administrator to be able to create and manage all users
I want a site administrator to be able to create and manage all orders
Given these requirements. My initial thoughts are to design endpoints in this manner.
# to request a new customer account
/customers/request {POST}
# create and view customers - limited to admins
/customers {GET, POST}
# view customer info, update a customer
/customers/{customer_id} {GET, PATCH}
# create and view orders for a customer
/customers/{customer_id}/orders {GET, POST}
# view and update order for a customer
/customers/{customer_id}/orders/{order_id} {GET, PATCH}
I feel pretty confident that those path's make sense and follow the general restful ideas. However, I'm not sure how to handle the users endpoint. The problem is, I want customer admins to be able to create users that can use their customer account to create orders. Where do customer admins POST to to accomplish this? I had a couple of ideas.
Following this answer, I thought about this.
# creation of users always done through this endpoint no matter what the
# authenticated user's role is
/users { GET, POST }
# associate user with customer
/customers/{customer_id}/user_memberships { GET, POST }
The problem with this approach is how does the admin of the customer account get the ID of the user to associate with the customer account. Any GET request on /users would be filtered by retrieving only users who are part of their customer account. However, because the user would be created before the membership, they would never be able to view the user.
I also though about just having two endpoints to create users.
# create a user for a customer account
/customers/{customer_id}/users {GET, POST}
# root users endpoint only accessible to admins
/users {GET, POST}
# return same user
/users/1
/customers/{customer_id}/users/1
It essentially boils down to using the customer url prefix as a means of authorization. It seems a little strange to have two endpoints invalidating the other. What if the root endpoints were only views of the subresource endpoints?
# view all users in system - admin only
/users {GET}
# create & view admin users
/admin/users {GET, POST}
# create internal office users
/locations/{location_id}/users { GET, POST }
# create customer users
/customers/{customer_id}/users { GET, POST }
In this case, we could still cache GET responses on the sub resources as they would not change unless there was a POST or PATCH/DELETE on the specific id of a subresource.
This style also seems to make sense for orders. Admins can view all orders even though they technically belong to a customer.
# admin can view all orders
/orders?customer_id=1234
/orders
I kind of like the idea of the root resource being a view of subresources allowing for easier authorization based on the url.
So, I guess after all of that, my real question is:
Is having multiple endpoints representing the same resource a problem even if one of them is just an aggregate view of the subresources and does not permit the creation of a resource through that endpoint?
You shouldn't mix the design of your API, REST principles, and the need for authorization. You should design your API in a way that makes it:
easy to use
easy to maintain
easy to understand
A RESTful approach to API design tries to address these different concerns. A RESTful approach is about identifying the objects you have, their state, and their possible transition.
And that's where it stops. Now, you wonder about authorization. You want to be able to control what a user can do on given records depending on who the user is (an administrator, a customer,...) and what the targeted resource is (a customer record...).
What you need to do is deploy an authorization framework on top of your REST API in a loosely-coupled way. In other words, you want to externalize authorization. You definitely not want to build authorization straight into your API. Imagine that suddenly you have new authorization rules / constraints: you would have to recode your API. In doing so you'd break all the clients. That would lead to poor user experience.
So, we've identified you need to externalize authorization. Great. What are the different ways to do so? This depends on the language and framework you use.
You can use:
Spring Security in Java
Yii in PHP
CanCan in Ruby
... and many more
You could also implement your own filters, for instance a Servlet filter in Java in front of your REST endpoints.
Lastly, you can turn to a full-blown attribute-based authorization model based on XACML. There are several open-source and vendor alternatives. If you are not familiar with attribute-based access control or XACML, have a look at the following links:
ABAC explained by NIST
XACML
With XACML, you define policies centrally e.g:
Administrators can view all customer accounts
Administrators can modify a customer account he/she is assigned to
Customers can view and edit their own account only
The policies are then evaluated in an authorization service (in XACML that's known as a policy decision point). The authorization service exposes a binary authorization API which your API can call out to: can user Alice view record foo?.
Using externalized authorization based on policies and using XACML, you achieve a loose coupling between your business logic (your business API) and the authorization logic which you can more easily maintain and update.
According to my understanding, for ex. u want that for particular customerId you want that this customer only view its users not will be able to create its user which will only be created by admin, so this can be done using spring security as well and this definitely creates the problem so u have to categorize the customer according to your requirement.