Id Encryption in spring-hateoas or Spring Rest Data - spring-data-rest

I have a question about a standard pattern or mechanism in spring-hateoas or Spring Rest Data about encrypting the IDs of the Resources/Entities.
The reason I am asking, a requirement to our project is that we don't deliver the id's of our objects to the outside world and they should not be used in GET Requests as Parameters.
I know, Spring Rest Data and spring-hateoas does not give the ids of the objects unless they are configured so but even that case I can see the ids in links.
I know I can use PropertyEditors or Converters to encrypt/decrypt ids before and after Json serialisation/deseritalisation but I just like to know is there a more standard way?
Thx for answers...

If you have the unique 'business id' property of your resource you can configure SDR to use it instead of the entity ID.
First you have to create lookup method of your entity with this unique property:
public interface MyEntityRepo extends JpaRepository<MyEntity, Long> {
#RestResource(exported = false)
Optional<CatalogResource> findByMyUniqueProperty(String myUniqueProperty);
}
Then use it to configure SDR:
#Component
public class DataRestConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.withCustomEntityLookup()
.forRepository(MyEntityRepo.class, MyEntity::getMyUniqueProperty, MyEntityRepo::findByMyUniqueProperty);
super.configureRepositoryRestConfiguration(config);
}
}
After this customization you will have resource URI like this:
http://localhost:8080/myEntities/myUniquePropertyValue1

Related

Field level access Control using Aspects/ Custom Annotation (Spring boot , Microservice)

Currently i am trying to implement authorization on fields , please find the cases from the below
example :
Based on some specific roles which are available in the ThreadLocal , we should be able to determine whether the user is allowed to pass the field in the payload. if the received role is not allowed to pass the attribute to do any Creation or updation we should throw 403
While providing response in the GET API , we should hide few fields which all are annotated with role:"ADMIN" as an example .
For the above example i am trying to use custom annotation Target as Fieldex :
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface CustomScopeFilter {
String[] scopesAllowed() default {"END_USER"};
}
But the custom annotation with FIELD is not working actually , because the implementation class which annotated with #Aspect is not getting called
The above annotation i have used in my DTO on field level
ex :
#Getter
#Setter
class TestDTO {
#CustomScopeFilter(scopesAllowed={"ADMIN"})
private String userRole;
}
Any ideas or suggestion would be very much helpful !! Thanks in advance
tried using Pointcut , joinpoint ,Aspectj, AOP. but those didnot worked on field-level
So i am expecting some suggestions how i can make it work .. or any alternative approach to achieve the same.

Spring Data Rest findByIdIn

We use Spring Data Rest to build simple API for our internal webapp.
Our repositories as below:
public interface BaseRepository<T, ID extends Serializable> {
List<T> findByIdIn(#Param("ids") Collection<ID> ids);
}
public interface FooRepository extends BaseRepository<Foo, String> {}
public interface BarRepository extends BaseRepository<Bar, Long> {}
Our client fetch data, passing ids parameters as below:
GET http://example.com/api/foos/search/findByIdIn?ids=ABC,XYZ --> It works well for String ids.
GET http://example.com/api/bars/search/findByIdIn?ids=1,2,3 --> Got exception for numeric ids.
We got the exception: Parameter value element[1] did not match expected type [java.lang.Long (n/a)]
What's wrong with above repositories? What is the correct way to pass numeric ids?
You may use this library which lets you build advanced search queries: https://github.com/turkraft/spring-filter
You can then simply do:
?filter= ids in (1, 2, 3, 4)
It supports enums, dates, booleans, logical operations, comparisons, and even joins (nested fields).

findBy URI not working in Spring Data Rest

By default, in Spring Data Rest the #Id of the entity is not exposed. In line with the REST rules, we're supposed to use the URI of the resource to refer to it. Given this assumption, the findBy queries should work if you pass a URI to them, but they don't.
For example, say I have a one-to-many relationship between Teacher and Student. I want to find students by teacher.
List<Student> findByTeacher(Teacher teacher)
http://localhost:8080/repositories/students/search/findByTeacher?teacher=http://localhost:8080/repositories/teachers/1
This doesn't work because the framework is attempting to convert the teacher URI to a Long.
I get this error that says "Failed to convert from type java.lang.String to type java.lang.Long".
Am I missing something?
You could expose #Id s by configuring web intializer
//Web intializer
#Configuration
public static class RespositoryConfig extends
RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config) {
config.exposeIdsFor(Teacher.class);
}
}
Its good to change List to Page
List findByTeacher(Teacher teacher)
to
Page<Student> findByTeacher(#Param("teacher) Teacher teacher, Pageable pageable);
Also note #Param annotation is required along with Pageable. The latter is required because return type "Page"
3.Latest snapshots, not milestones work fine
See https://jira.spring.io/browse/DATAREST-502
Depending of your version of Spring Data, it would work as you want or not. If you are with Spring Data 2.4, you need to pass the URI. If you are with a previous version, you need to pass the id.

wcf data services - expose properties of the same type in data model

I'm new to WCF data services. I have a quite simple data model. Some of its properties have the same type, like this:
public IQueryable<IntegerSum> HouseholdGoodsSums
{
get
{
return GetData<IntegerSum>(DefaultProgramID, "rHouseholdGoodsPrice", IntegerSumConverter);
}
}
public IQueryable<IntegerSum> StructureSums
{
get
{
return GetData<IntegerSum>(DefaultProgramID, "rStructurePrice", IntegerSumConverter);
}
}
The IntegerSum is a very very simple class:
[DataServiceKey("Amount")]
public class IntegerSum
{
public int Amount { get; set; }
}
When I navigate to my service in a web browser, I see the following error message:
The server encountered an error processing the request. The exception message is 'Property 'HouseholdGoodsSums' and 'StructureSums' are IQueryable of types 'IntegrationServices.PropertyIntegrationServices.IntegerSum' and 'IntegrationServices.PropertyIntegrationServices.IntegerSum' and type 'IntegrationServices.PropertyIntegrationServices.IntegerSum' is an ancestor for type 'IntegrationServices.PropertyIntegrationServices.IntegerSum'. Please make sure that there is only one IQueryable property for each type hierarchy.'.
When I get rid of one of these two properties, the service starts working.
I searched for this error message in google, but haven't found a solution.
Is it really not allowed to have two properties with the same type in a data model? If so, why?
Comrade,
To address the error first, you're running into a limitation in the Reflection provider. Specifically, the Reflection provider doesn't support MEST.
That said, there are better approaches to achieve what you're trying to achieve. You should probably not make IntegerSum an entity type (an entity type is a uniquely identifiable entity, which doesn't really fit your scenario). While you can't expose that directly, you can expose it as a service operation. That seems much closer to what you're trying to achieve.
A couple of ways to distinguish between whether or not something should be an entity:
If it has a key already, such as a PK in a database, it should probably be an entity type
If you need to create/update/delete the object independently, it must be an entity type
HTH,
Mark

Filter contents of lazy-loaded collection with NHibernate

I have a domain model that includes something like this:
public class Customer : EntityBase<Customer>, IAggregateRoot
{
public IList<Comment> Comments { get; set; }
}
public class Comment : EntityBase<Comment>
{
public User CreatedBy { get; set; }
public bool Private { get; set; }
}
I have a service layer through which I retrieve these entities, and among the arguments passed to that service layer is who the requesting user is.
What I'd like to do is be able to construct a DetachedCriteria in the service layer that would limit the Comment items returned for a given customer so the user isn't shown any comments that don't belong to them and are marked private.
I tried doing something like this:
criteria.CreateCriteria("Comments")
.Add(Restrictions.Or(Restrictions.Eq("Private", false),
Restrictions.And(Restrictions.Eq("Private", true),
Restrictions.Eq("CreatedBy.Id", requestingUser.Id))));
But this doesn't flow through to the lazy-loaded comments.
I'd prefer not to use a filter because that would require either interacting with the session (which isn't currently exposed to the service layer) or forcing my repository to know about user context (which seems like too much logic in what should be a dumb layer). The filter is a dirty solution for other reasons, too -- the logic that determines what is visible and what isn't is more detailed than just a private flag.
I don't want to use LINQ in the service layer to filter the collection because doing so would blow the whole lazy loading benefit in a really bad way. Lists of customers where the comments aren't relevant would cause a storm of database calls that would be very slow. I'd rather not use LINQ in my presentation layer (an MVC app) because it seems like the wrong place for it.
Any ideas whether this is possible using the DetachedCriteria? Any other ways to accomplish this?
Having the entity itself expose a different set of values for a collection property based on some external value does not seem correct to me.
This would be better handled, either as a call to your repository service directly, or via the entity itself, by creating a method to do this specifically.
To fit in best with your current model though, I would have the call that you currently make to get the the entities return a viewmodel rather than just the entities;
public class PostForUser
{
public Post Post {get; set;}
public User User {get; set;}
public IList<Comment> Comments}
}
And then in your service method (I am making some guesses here)
public PostForUser GetPost(int postId, User requestingUser){
...
}
You would then create and populate the PostForUser view model in the most efficient way, perhaps by the detached criteria, or by a single query and a DistinctRootEntity Transformer (you can leave the actual comments property to lazy load, as you probably won't use it)