Null values inside #PreAuthorize giving error - spring

I'm trying to check if the user has role "ADMIN" and if he is the project's creator before giving him access to change the project like this:
#PreAuthorize("hasRole('ROLE_ADMIN') and this.projectService.findById(#id).getAdmin().getUsername() == authentication.name")
#RequestMapping(value = "/projects/{id}", method = RequestMethod.PUT)
public ResponseEntity<?> doUpdateProject(#PathVariable("id") Long id, #RequestBody ProjectDto project)
throws InstanceNotFoundException, DuplicatedResourceException {
Project updatedProject = projectService.update(id, project.getName(), project.getDescription());
return ResponseEntity.ok(updatedProject);
}
This works, only allowing an admin to update the project if he is the owner. But I noticed that if I make the call to a non-existent project, I get a 500 Internal Server Error instead of a 404 Not Found:
Servlet.service() for servlet [dispatcherServlet] in context with path [/tasks-service] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: Failed to evaluate expression 'hasRole('ROLE_ADMIN') and this.projectService.findById(#id).getAdmin().getUsername() == authentication.name'] with root cause.
I think that it is because of pointing to a null value (projectService.findById(#id)) but even when checking if the value is null, using for example a ternary operator that returns "true" if the value is null, I keep getting the same error.
#PreAuthorize("hasRole('ROLE_ADMIN') and this.projectService.findById(#id) != null ? this.projectService.findById(#id)?.getAdmin().getUsername() == authentication.name : true")
My idea here is to "pass" the #PreAuthorize condition when the project ID specified does not exist in order to get a 404 error later, but the same internal error is happening.
What should I do to get a 404 Not Found in this case?

Related

Can a LINQ query with a where clause on the key to an OData service be done with filter query option instead of a canonical URL?

The problem
I'm trying to query data from an OData V4 service. This is done with a C# client generated by the OData Connected Service extension for Visual Studio. The query is done with a LINQ expression with a where clause. The where clause contains criteria for the key fields of the entity being queried.
The query results in a DataServiceQueryException ("An error occurred while processing this request") with an inner DataServiceClientException ("NotFound"), both from the Microsoft.OData.Client namespace. See below for the full stack trace.
Analysis
Using Fiddler I can see that the request being sent is using a canonical URL (also called a by-key request). If the criteria values do not match any existing data, the response has the code 404 Not Found. This code seems to cause the exception.
When the where clause is changed to also include non-key fields, the request is sent using a $filter query option. In this case, if the criteria values do not match any existing data, the response has the code 200 OK. This does not cause an exception and returns null as result of the LINQ query.
Another workaround is to not use LINQ and instead specify explicitely that a filter query option should be used.
Comparison with the OData reference service TripPin showed that the 404 response does not seem to be the correct response in this case. TripPin instead returns 204 No Content. While the OData specification has several indications that this seems the correct response in this case, I could not find an explicit statement to that effect. In any case, this point is moot since I don't have control over the OData service and can't change its behavior.
Repro details
Unfortunately, the OData service in question is not publicly available. It may be possible to mock such a service or find a public service that shows the same behavior. I have not looked into this since I found a solution (see my answer).
Nevertheless, here is the code that causes the exception:
static void GetData()
{
Uri odataUri = new Uri("https://the-odata-service", UriKind.Absolute);
// Resources is a class generated by the OData Connected Service extension
// and extends Microsoft.OData.Client.DataServiceContext
Resources context = new Resources(odataUri);
var entity = context.Entities.Where(x => x.Key == 1).SingleOrDefault();
}
Producing this request and response:
GET https://the-odata-service/entities(1) HTTP/1.1
HTTP/1.1 404 Not Found
The exception:
Unhandled exception. Microsoft.OData.Client.DataServiceQueryException: An error occurred while processing this request.
---> Microsoft.OData.Client.DataServiceClientException: NotFound
at Microsoft.OData.Client.QueryResult.ExecuteQuery()
at Microsoft.OData.Client.DataServiceRequest.Execute[TElement](DataServiceContext context, QueryComponents queryComponents)
--- End of inner exception stack trace ---
at Microsoft.OData.Client.DataServiceRequest.Execute[TElement](DataServiceContext context, QueryComponents queryComponents)
at Microsoft.OData.Client.DataServiceQuery`1.GetEnumerator()
at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found)
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at Microsoft.OData.Client.DataServiceQueryProvider.ReturnSingleton[TElement](Expression expression)
at System.Linq.Queryable.SingleOrDefault[TSource](IQueryable`1 source)
at <my test program in the GetData method in the line of the SingleOrDefault call>
If I change the LINQ to
var entity = context.Entities
.Where(x =>
x.Key == 1
&& x.AnotherNonKeyField == "2")
.SingleOrDefault();
I get
GET https://the-odata-service/Entities?$filter=Key%20eq%201%20and%20AnotherNonKeyField%20eq%20'2'&$top=2 HTTP/1.1
HTTP/1.1 200 OK
{
"#odata.context":"https://the-odata-service/$metadata#Entities","value":[
]
}
which does not result in an exception, but entity being null.
The question
To sum up, while there are workarounds, I would prefer if I could query the odata service with LINQ and without having to add dummy criteria (which would not always be possible). Is there a way to do that?
TLDR
The KeyComparisonGeneratesFilterQuery property of the DataServiceContext can be used to generate a $filter query option.
Some more background
I spent some time researching this issue in context of LINQ and the client that was generated. In hindsight, it is obvious that the Microsoft OData Client library would have been a better place to start, since it throws the exception. But who has time to read a stack trace when instead you can furiously google and debug for a few hours *sigh* ?
Eventually I found my way to issue #851 DataServiceQuery makes a "by key" request when Where clause compares just the ID, causing exception instead of empty result if the entity is not found. and pull request #1762 Enable Where clause to generate $filter query options for key predicates. Especially the later does a much better job of explaining the purpose and how to use the KeyComparisonGeneratesFilterQuery property than the documentation.
With that, the above code can be fixed like this:
static void GetData()
{
Uri odataUri = new Uri("https://the-odata-service", UriKind.Absolute);
// Resources is a class generated by the OData Connected Service extension
// and extends Microsoft.OData.Client.DataServiceContext
Resources context = new Resources(odataUri);
context.KeyComparisonGeneratesFilterQuery = true;
var entity = context.Entities.Where(x => x.Key == 1).SingleOrDefault();
}
Which produces
GET https://the-odata-service/Entities?$filter=Key%20eq%201&$top=2 HTTP/1.1
HTTP/1.1 200 OK
{
"#odata.context":"https://the-odata-service/$metadata#Entities","value":[
]
}

RepresentationModelProcessor nullpointer having mandatory field in controller spring 2.7.4

I have a class implementing RepresentationModelProcessor that's adding links to a resource:
linkTo(methodOn(MyController.class).findMyElements(file.getId(), null, null))
.withRel("filterFiles"))
The controller looks like this:
#RequestMapping(method = { POST, GET }, value = "/myFiles/{fileId}/filterFiles")
public List<FileDto> findFilterFiles( #PathVariable("fileId") final Long fileId,
#RequestParam(name = "fileType") final Long fileType,
#RequestBody(required = false) final FileFilterDto filter)
In Spring 2.6.4, it used to work. Now, I've tried upgrading to 2.7.4 and it does not work anymore. I've tracked the issue down and it seems like the required request param must not be null anymore.
I'm getting a null pointer exception with text like this:
Illegal character in query at index 93: http://localhost:8080/files/10013/filterFiles?fileType={fileType}
with the index pointing to '=' of fileType={fileType} .
Is it a bug? If yes - how can I fix it? Passing a constant number fixes the null-pointer exception:
linkTo(methodOn(MyController.class) .findMyElements(file.getId(), 1L, null))
.withRel("filterFiles"))
but it leads to incorrect code.
I've raised an issue here: https://github.com/spring-projects/spring-hateoas/issues/1872 but it might be the wrong repository after all.

incompatible data type in conversion: from SQL type VARCHAR to java.lang.Long

ERROR [hybrisHTTP15] [FlexibleSearch] Flexible search error occured...
Aug 16, 2022 5:37:53 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [DispatcherServlet] in context with path [/trainingstorefront] threw exception [Request processing failed; nested exception is de.hybris.platform.servicelayer.search.exceptions.FlexibleSearchException: SQL search error - enable the property 'flexible.search.exception.show.query.details' for more details] with root cause
org.hsqldb.HsqlException: incompatible data type in conversion: from SQL type VARCHAR to java.lang.Long, value: customer10
at org.hsqldb.error.Error.error(Unknown Source)
In my SAP Hybris flow, I'm hitting the url from the Controller. While the data is being retrieved from the DB, I am getting the above mentioned error.
P.S.: the customer name "customer10" is being loaded correctly but facing this issue. The code to retrieve data from DB is attached below:
#Override
public List<NewCustomerModel> getCustomerNameFromID() {
final StringBuilder query=new StringBuilder();
query.append("SELECT {" + NewCustomerModel.NAME + "} FROM {" + NewCustomerModel._TYPECODE + "} WHERE { "+ NewCustomerModel.CUSTID + "}=1");
final FlexibleSearchQuery flexQuery = new FlexibleSearchQuery(query);
final SearchResult<NewCustomerModel> searchResult = flexibleSearchService.search(flexQuery);
if (CollectionUtils.isNotEmpty(searchResult.getResult()))
{
return searchResult.getResult();
}
return Collections.EMPTY_LIST;
}
Please let me know if there is any error in the query or is there any configuration issue?

How to improve error responses when using #RepositoryRestResource

I'm using spring's #RepositoryRestResource annotation on a PagingAndSortingRepository.
When I send an erroneous payload to the corresponding endpoint, the error responses that are sent back are hard to parse, e.g.
{
"cause": {
"cause": {
"cause": null,
"message": "ERROR: duplicate key value violates unique constraint \"uk_bawli8xm92f30ei6x9p3h8eju\"\n Detail: Key (email)=(jhunstone0#netlog.com) already exists."
},
"message": "could not execute statement"
},
"message": "could not execute statement; SQL [n/a]; constraint [uk_bawli8xm92f30ei6x9p3h8eju]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement"
}
Is there any way to configure the messages, so it is clear which field (here: email) caused the error?
Regarding the error handling - you can implement a custom exception handler for such exceptions, extract the constraint name from the root cause, analyze it and create a corresponding message for the user.
Some error handling examples: 1, 2.
UPDATED
You should check the app log to determine which exception you have to handle. If I'm not mistaken for constraint violation we must handle org.springframework.dao.DataIntegrityViolationException, for example:
#ControllerAdvice
public class CommonExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(DataIntegrityViolationException.class)
ResponseEntity<?> handleDataIntegrityViolation(DataIntegrityViolationException ex, HttpServletRequest req) {
String causeMessage = NestedExceptionUtils.getMostSpecificCause(ex).getMessage(); // determine the root cause message
String reqPath = req.getServletPath(); // get the request path
String userMessage = ... // Decide what the message you will show to users
HttpStatus status = HttpStatus... // Decide what the status your response will be have, for example HttpStatus.CONFLICT
ApiErrorMessage message = new ApiErrorMessage(userMessage, status, reqPath); // Create your custom error message
return new ResponseEntity<>(message, status); // return response to users
}
// other handlers
}
Or you can implement this handler easier as in the official example.

What is nextMessageIdToRead parameter in browseQueue() method of AndesAdminServicePortTypeProxy class

I am using on WSO2 Message Broker as a message brokering system in my project. To get the queue browse information, I generated client from AndesAdminServics WSDL and I am trying to call browseQueue() method of AndesAdminServicePortTypeProxy class from my api. Generated browseQueue() method is
public org.wso2.carbon.andes.admin.internal.xsd.Message[] browseQueue(java.lang.String queueName, java.lang.Long nextMessageIdToRead, java.lang.Integer maxMsgCount) throws java.rmi.RemoteException{
if (andesAdminServicePortType == null)
_initAndesAdminServicePortTypeProxy();
return andesAdminServicePortType.browseQueue(queueName, nextMessageIdToRead, maxMsgCount);
}
browseQueue() method takes three parameter -
java.lang.String queueName, java.lang.Long nextMessageIdToRead, java.lang.Integer maxMsgCount
I understand parameter queueName, but i am not getting, What does parameters nextMessageIdToRead and maxMsgCount represent. At the time of browseQueue() method call, What should i pass as a parameter.
You can refer to the actual admin service code. There it contains more information
https://github.com/wso2/andes/blob/b721d5cccfd9896ec871610b7938ba96785b202c/modules/andes-core/management/common/src/main/java/org/wso2/andes/management/common/mbeans/QueueManagementInformation.java#L162
name = "queueName", description = "Name of queue to browse messages"
name = "lastMsgId", description = "Browse message this onwards"
name = "maxMsgCount", description = "Maximum message count per request"

Resources