TL;DR: How do I implement fine-grained access control in the flattened REST api approach that Spring-Data-Rest gives us?
So - I'm making an API using Spring-Data-Rest where there's three main access levels:
1) The admin - can see/update all groups
2) An owner of a group - can see/update the group and everything under it
3) An owner of a sub-group - can see/update only his group. No recursive nesting, just one sub-level allowed.
And 'group' is exposed as a resource (has a crud repository).
So far so good - and I've implemented some access control for modification using a Repository Event Handler - so on the create/write/delete side I think I'm fine.
Now I need to get to the point of limiting visibility of some of the items. This is ok for getting a single item since I can use Pre/Post Authorize annotations and reference the principal.
The problem lies in the findAll() methods - I don't have an easy hook to filter out the specific instances I don't want exposed based on the current principal. For example - a sub-group owner could see all groups by doing GET /groups. They should ideally have the items they don't have access to not even be visible at all.
To me this sounds like writing custom #Query() annotations on the repository interfaces, but that doesn't seem doable because:
I need to reference the principal in the query. SPeL is supposed to be supported, but doesn't seem to work at all with ?# expressions (despite this blog post suggesting otherwise: https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions). I am using spring-boot with 1.1.8.RELEASE and the Evans-RELEASE train for spring-data generally.
The kind of query I need to write is going to be different depending on the access level, which can't realistically be encompassed in a single JPQL statement (if admin select all groups, else get all (sub)groups associated with the principal's user).
Therefore it sounds like I need to write some custom repository implementations for that and just reference the principal in code. Well that's ok - but it seems like a lot of work for each repository that I need to control the access to (I think this will be almost all of them). This applies to findAll and various custom search methods.
Am I approaching this wrong? Is there another approach to dynamically limiting item visibility based on the currently logged-in user that would work better? In a flat namespace like spring-data-rest exposes, I would imagine this would be a common problem.
In a prior design I just solved it by exposing everything under /api/groups/{groupId}/... and had a sub-resource locator act as a single pinch-point to control access to anything under it. No such luck in spring-data-rest.
Update: now stumbling with a custom method overriding findAll() (this works for other methods defined on my custom interface). Though this might be a separate question - I'm blocked right now. Spring-data is just not calling this when I do a GET /groups, but calling the original. Oddly enough it does use my query if I define one on the interface and mark it with #Query (perhaps custom overrides of built-in methods aren't supported anymore?).
public interface GroupRepository extends JpaRepository<Group, Long>, GroupCustomRepository {}
public interface GroupCustomRepository {
Page<Group> findAll(Pageable pageable);
}
public class GroupCustomRepositoryImpl extends SimpleJpaRepository<Group, Long> implements GroupCustomRepository {
#Inject
public GroupCustomRepositoryImpl(EntityManager em) {
super(Group.class, em);
}
#Override
public Page<Group> findAll(Pageable pageable) {
MyPrincipal principal = (MyPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Page<Group> result;
if (principal.isAdmin()) {
result = findAll(pageable);
} else {
Specification<Group> spec = (root, query, cb) -> cb.or(
cb.equal(root, principal.getGroup()),
cb.and(cb.isNotNull(root.get(Group_.parentGroup)), cb.equal(root.get(Group_.parentGroup), principal.getGroup()))
);
result = findAll(spec, pageable);
}
return result;
}
}
Update 2: Since I can't access the principal in the #Query, and I can't override it with a custom method, I'm at a brick wall. #PostFilter doesn't work either because the return object is a Page rather than a collection.
I've decided to just wall-off the /groups to admins only, and have everyone else use different approaches (/groups/search/somethingSpecific) with #PostFilters/#PostAuthorizations.
This doesn't seem like it meshes very well with the HAL approach though. Interested in how other people are solving these kinds of issues with Spring-data-rest.
We ended up approaching this as follows:
We created a custom aspect which sits in front of the CRUD methods on a repository. It then looks up and calls an associated 'authorization handler' which is annotated on the repository that dynamically manages authorization details.
We had to be pretty heavy-handed when it came to limiting results in a findAll() query (eg: looking at /users) - essentially, only admins could list all of anything sensitive. Otherwise limited users had to use query methods for specific items.
We created some reusable authorization-related classes, and use those in certain scenarios - particularly custom queries, eg:
#PreAuthorize("#authorizations.systemAdminRead()")
#Query("select u FROM User r where ...")
List findAll();
#PostAuthorize("#otherAuthorizationHandler.readAllowed(returnObject)") ResponseObject someQuery();
All in all, it works - but it feels very clunky, and it's easy to miss things. I do wish this was baked-in to the framework more, even being able to dynamically adjust the default queries would be useful (when I was attempting this, I wasn't able to have the queries updated appropriately with #Query).
We happen to be using PostgreSQL, so the upcoming row level security (http://michael.otacoo.com/postgresql-2/postgres-9-5-feature-highlight-row-level-security/) would have fit the bill nicely, assuming we could feed it the proper authorization details via the DB connection.
Related
I know this has been asked quite a long time.
This was answered at 2012-03-16.
Repository pattern - Why exactly do we need Interfaces?
I never used repository one month ago. I use laravel: Controller, Service, Model, View.
Several months ago, I start to use trait. So many articles talk about interface, ok, one month ago, I start to use repository with interfaces. Now I feels that I'm doing things that seems not necessary.
There is Order model I forgot to draw. And I have to bind interface with repository in RepositoryServiceProvider
public function register()
{
$this->app->bind(RepositoryInterface::class, Repository::class);
$this->app->bind(MemberRepositoryInterface::class, MemberRepository::class);
$this->app->bind(OrderRepositoryInterface::class, OrderRepository::class);
$this->app->bind(OrderItemRepositoryInterface::class, OrderItemRepository::class);
//...
}
Now go back to that question's answer in 2012. Why we need to use interfaces? Because:
public class EmployeeRepositoryEF: IEmployeeRepository
{
public Employee[] GetAll()
{
//here you will return employees after querying your EF DbContext
}
}
public class EmployeeRepositoryXML: IEmployeeRepository
{
public Employee[] GetAll()
{
//here you will return employees after querying an XML file
}
}
public class EmployeeRepositoryWCF: IEmployeeRepository
{
public Employee[] GetAll()
{
//here you will return employees after querying some remote WCF service
}
}
But with the bindings in RepositoryServiceProvider, we can not use these different repositories at the same time. I cannot imaging how. We have to change the bindings. But if so, why not just change the type hint in service layer?
Ok, I saw many articles actually use:
Controller > SomeRepositoryInterface $someRepository > Model
They don't have service layer.
Does it mean, since I have service layer, So I don't need interface?
Controller > SomeService $someService> SomeRepository $someRepository > Model
If we want to change repository, just do:
In SomeService:
use App\Repositories\Abc\SomeRepository;
or
use App\Repositories\Xyz\SomeRepository;
Then
use App\Repositories\Eloquent\Sale\OrderRepository;
use App\Repositories\Eloquent\Sale\OrderItemRepository;
use App\Repositories\Eloquent\Sale\RmaRepository;
use App\Repositories\Eloquent\Member\MemberRepository;
use App\Repositories\Eloquent\Member\MemberGroupRepository;
or
use App\Repositories\MSSQL\Sale\OrderRepository;
use App\Repositories\MSSQL\Member\MemberRepository;
or
use App\Repositories\Oracle\Sale\OrderRepository;
use App\Repositories\Oracle\Member\MemberRepository;
Eloquent can change driver to use mssql or oracle. Then...
use App\Repositories\DbBuilder\Sale\OrderRepository;
use App\Repositories\DbBuilder\Member\MemberRepository;
or
use App\Repositories\RawSql\Sale\OrderRepository;
use App\Repositories\RawSql\Member\MemberRepository;
or
use App\Repositories\AnyOtherKind\Sale\OrderRepository;
use App\Repositories\AnyOtherKind\Member\MemberRepository;
Can someone give me some suggestion?
Because we need a contract for the classes that people create over time. They should implement the same thing, and the customer should be able to use them interchangeably. If you don't have an interface, the next developer might forget implementing some methods and this means bad!
For example we have implemented our repositories to work with MySQL, and we exit the company. After a year, they plan for using something else rather than MySQL, so they have to implement new repositories that are compatible with the previous repositories and this is why we need an interface.
I hope my answer is simple and clear.
To begin with, using Repository pattern is bad with Active Record (which is Eloquent). AR (active record) models already have all the methods to all possible CRUD operations and scopes to encapsulate logic within. Using repositories over them is a good example of overengineering, so I'd recommend not using them at all in Laravel.
When working with DM (data-mapper) models, repositories are used to switch between different databases (like in your example in RepositoryServiceProvider). So, in case there is a need to change database over the project, you just create another implementation of repository for different database type. And again, in Laravel this is already done at query builder level, so you just don't need to do that by yourself.
Question: Any HAL clients or examples of accessing HAL API with admin-on-rest ?
I got started because HAL was mentioned in the first paragraph of the introduction, but now I'm having trouble finding any examples or anyone else using HAL rest client, so I am winding up for now just writing a bunch of simple findAll repositories on top of the already robust existing HAL API.
Adding a more concise answer here that isn't polluted with my thought process now that I've got it all figured out (for anyone's future reference)... Again assuming the HAL API was made with Spring Data Rest.
The four major keys to this integration are:
Exposing foreign key attributes in your JPA entities, which is required in several places by admin-on-rest #Column(name="parentEntity", updatable=false, insertable=false) private Integer parentEntityId;
Exposing all your entity IDs using RepositoryRestConfiguration.exposeIdsFor( MyEntity.class )
Annotate your repositories as #RepositoryRestResource and have them extend PagingAndSortingRepository<MyEntity, Integer>, QueryDslPredicateExecutor<MyEntity> to expose extremely useful search filters by attribute name (e.g. /api/myEntitys?field1=foo&field2=bar).
When submitting create and save requests with foreign keys make sure to adjust your params.data to include the linked resource (e.g. 'http://myserver.com/api/myEntitys/19') on top of (or in place of, HAL has no use for it) the foreign key you exposed in 1. (e.g. myEntityId=19)
Other small items of note:
use PATCH instead of PUT when updating (you may be able to use PUT if you are more of a hibernate expert and can map your entities better than I can but I had trouble getting it mapped perfectly and HAL's PATCH will take partial entities)
When submitting GET_LIST and GET_MANY_REFERENCE you get the total number of items and pagination parameters from the 'page' section of the response, and you use 'size' and 'page' query params in your API requests. (so, no need for headers and stuff)
To change the default 'equals' filter for any string entries (from 3. above) to a 'contains' filter, you will have to also extend QuerydslBinderCustomizer<QMyEntity> and provide your own customize method in each of your repositories. For example:
default void customize( QuerydslBindings bindings, QChampion champion )
{
bindings.bind( String.class ).first( ( StringPath path, String value ) -> path.contains( value ) );
}
We don't have any examples for HAL specifically. However, the point of this introduction was that admin-on-rest is backend agnostic.
You can create your own custom rest client by following the documentation. Read the code of existing ones for inspiration.
For anyone referencing this in the future, if you happen to be in control of your API through Spring Data Rest you can consider the use of an excerptProjection on every one of your existing repositories that shows an inline version of your entity. This would work if there were absolutely nothing besides admin-on-rest accessing your API.
For my case I am planning on writing a custom projection for every rest resource that has entities and naming it the same thing: "inline". Then in the admin-on-rest restClient, just always asking for the inline projection on every GET_MANY or GET_MANY_REFERENCE request.
This is the best I have at the moment. It's not perfect but for the amount of entities I have it's still many weeks faster than building a CRUD interface from scratch so I highly recommend admin-on-rest.
Right now I can't get the concept behind Spring Data REST if it comes to complex aggregate roots. If I understand Domain Driven Design correctly (which is AFAIK the base principle for spring data?), you only expose aggregate roots through repositories.
Let's say I have two classes Post and Comment. Both are entities and Post has a #OneToMany List<Comment> comments.
Since Post is obviously the aggregate root I'd like to access it through a PostRepository. If I create #RepositoryRestResource public interface PostRepository extends CrudRepository<Post, Long> REST access to Post works fine.
Now comments is renderd inline and is not exposed as a sub resource like /posts/{post}/comments. This happens only if I introduce a CommentRepository (which I shouldn't do if I want to stick to DDD).
So how do you use Spring Data REST properly with complex domain objects? Let's say you have to check that all comments does not contain more than X characters alltogether. This would clearly be some invariant handled by the Post aggregate root. Where would you place the logic for Post.addComment()? How do you expose other classes as sub resources so I can access /posts/{post}/comments/{comment} without introducing unnecessary repositories?
For starters, if there is some constraint on Comment, then I would put that constraint in the constructor call. That way, you don't depend on any external validation frameworks or mechanisms to enforce your requirements. If you are driven to setter-based solutions (such as via Jackson), then you can ALSO put those constraints in the setter.
This way, Post doesn't have to worry about enforcing constraints on Comment.
Additionally, if you use Spring Data REST and only define a PostRepository, since the lifecycle of the comments are jointly linked to the aggregate root Post, the flow should be:
Get a Post and its collection of Comment objects.
Append your new Comment to the collection.
PUT the new Post and its updated collection of Comment objects to that resource.
Worried about collisions? That's what conditional operations are for, using standard HTTP headers. If you add a #Version based attribute to your Post domain object, then every time a given Post is updated with a new Comment, the version will increase.
When you GET the resource, Spring Data REST will include an E-Tag header.
That way, your PUT can be conditionalized with an HTTP If-Match: <etag> header. If someone else has updated the entity, you'll get back a 412 Status code, indicating you should refresh and try again.
NOTE: These conditional operations work for PUT, PATCH, and DELETE calls.
I have an Spring MVC application, which as it stands, exposes the IDs of JPA entities to users (in hidden html inputs or browser urls).
This could allow a malicious user to perform operations on entities belonging to another user using their browser.
Can anyone please suggest a solution to this security problem?
Is encrypting/decrypting IDs a good solution?
If so, in which layer (web, service, repository) is it appropriate to do this?
Which encryption solution is recommended (symetric/asymetric)?
Is there a better solution?
There is a better solution. You can keep your user ID's as primary keys for some purposes, but for this particular purpose I would suggest create a column in all the tables you need, for e.g. called: IDENTIFIER and generate some strong random ID for it, I am using this to generate ID's:
public static String generateId() {
return UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
}
Then you can use these identifiers in your views. I also wrote a generic method for JPA to find entities which have these kind of columns:
public T findByGeneratedId(String generatedId) {
CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery();
Root<T> entity = cq.from(entityClass);
CriteriaQuery query = cq.select(entity).where(
cb.equal(entity.get("generatedId"), generatedId));
try {
return (T) this.entityManager.createQuery(query).getSingleResult();
} catch (RuntimeException e) {
return null;
}
}
Note that my column is called GENERATED_ID and all entities has a field:
#Column(name = "GENERATED_ID", nullable = false, unique = true)
private String generatedId = generateId();
This will guarantee the uniqueness and safeness across your entities and there is no need for some complex encoding/decoding stuff.
In my opinion, encrypting IDs is not a good idea, more like hiding the real problem. And it would probably be quite tricky to do cleanly. And a malicious user could still intercept another user's requests and use the encrypted Ids to perform attacks.
The real solution is to implement some kind of access control in your business logic, and refuse attempts to access unauthorized resources, such as en entity belonging to another user.
You could implement this logic yourself if it is simple (no shared entities belonging to several users, no groups, just entities belonging to one user, that should be quite straightforward).
You could implement it as a sort of interceptor (using aspect-oriented, add an aspect to your DAO or service methods for example) in order to do it automatically and avoid too much repetitive boilerplate code.
You could also use Spring Security which has some mechanisms for Access Control.
If the needs are more complex, Spring Security can be used to implement a full ACL (Access Control List) system on your domain objects. This is more complex because ACLs are stored separately, so it needs some exxtra infrastructure in the database, and it seems quite complex to configure right, but it is the more flexible and scalable solution in my opinion. I haven't implemented ACLs myself though, so I can't offer much concrete advice on this.
If you insist on hiding the ids from the users, I suggest you don't really encrypt the IDs but use a per-session correspondance table between the real IDs and some randomly generated temporary ones. This way you avoid frequent crypting/decrypting of IDs and make one visible id totally useless for another user.
Hope this helps.
I like OData and I was particularly pleased to its adoption by the ASP.NET Web API.
I've created a few services for internal applications to consume, but never for public consumption. The primary reason is that the open nature of OData seems to make it very hard to make "safe" against abuse.
Most specifically, I'm worried that given the power to run arbitrary queries, a user could express a complex query which stresses the operational system to the point where the experience is bad for all other users.
In a WebApi controller, an OData endpoint is exposed as follows:
public class OrderController
{
[Queryable]
public IQueryable<Orders> Get()
{
// Compose and return the IQueryable<Orders>
}
}
This gives full control over the process of composition and execution of the query, but does so though the complex IQuerable<T> interface. It makes it trivial to give the user a subset of the information, e.g. append a Where to only include the records they have permission to access.
Is there an IQueryable<T> implementation that can wrap an existing IQuerable<T> instance to provide restrictions on the queries a user can run? I'm most interested in restricting the complexity of the query, but I also want to be able to prevent a user traversing associations to resources they shouldn't have access to.
I think you'll be glad to learn that in RTM, we've added options to let you customize what kind of querying you want to expose to users. So you can do this for example:
[Queryable(
AllowedFunctions = AllowedFunctions.AllStringFunctions,
AllowedLogicalOperators = AllowedLogicalOperators.Equal,
AllowedOrderByProperties = "ID")]
and restrict your query in a few common ways. If you want to restrict your query even further, there are validation hooks you can plug into, like ODataQueryValidator or by overriding the ValidateQuery method on the [Queryable] attribute.
You can use our nightly builds to get access to these features, or build the latest bits yourself.
Instead of using the Queryable attribute (which you are missing), you can not use this attribute and instead manually accept the ODataQueryOptions parameter, which gives you access to the various filter, top, etc options to permit validation of them.
Any function being called by a OData query can be passed an argument ODataQueryOptions like:
public IQueryable<T> Get(ODataQueryOptions options)`
{
//Use the following vars to fetch the values, and check if
//they are as you expect them to be. etc.
options.Top.RawValue;
options.Filter.Value;
options.Filter.ApplyTo();
}
In this case you can skip the [Queryable] attribute and use ApplyTo to manually apply the various queries over the result. :)