Restlet get remaining path when Router uses MODE_STARTS_WITH - java

I am creating a Restlet based service that matches an incoming request with a specific initial path. In the ServerResource that handles match I want access to the remaining path.
Router parentRouter;
parentRouter.attach("/svc/", new MyService());
parentRouter.attach("/svc/1.0/", new MyService());
public class MyService extends Router
{
public MetricsService()
{
attach("/m/", MyResource.class).setMatchingMode(Template.MODE_STARTS_WITH);
}
}
public class MyResource extends ServerResource
{
#Override
protected void doInit() throws ResourceException
{
String remainingPath = ??? <-- how do I do this?
}
#Get
protected void doit() throws ResourceException
{
...
}
}
Now for example the following requests result in the following remaining paths:
http://localhost/svc/m/ --> ""
http://localhost/svc/m/a --> "a"
http://localhost/svc/v1.0/m/some/m/path --> "some/m/path"
Of course there are always workarounds to get this fixed, but there is likely a more elegant of retrieving this using the existing Restlet framework. Specifically since Routers are already aware of remaining paths.
Any help or suggestions appreciated.

After some fiddling i found the answer:
#Override
protected void doInit() throws ResourceException
{
String remainingPath = getReference().getRemainingPart();
}

Related

Managing endpoint security and testing approaches

I am working on a small project that includes signing up an account, logging a user in, and request data via certain endpoints. The authentication piece is done via Spring security session Ids. The endpoints consist of publicly available endpoints (i.e. signing up, or forgot password), and some endpoints that require the user to be signed in (i.e. change password, get some content, etc). Kind of like this:
#RestController
public class FightController {
//publicly available
#GetMapping("/public/foo")
String methodForEveryone() {
return "Hi common dude";
}
#GetMapping("secret/bar")
String methodForSpecialPeople() {
return "What happens in fight controller...";
}
}
Spring security then has a list of public endpoints in a static WHITE_LIST
private static final String[] AUTH_WHITELIST = {
//public endpoints
"/public/foo", "swagger", "etc"
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(customAuthFilter(), RequestHeaderAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.antMatchers("/**").authenticated()
.and()
Tests are currently being done by hitting every endpoint and determining whether it is behaving as expected (via custom antmatchers in the WebSecurityConfigurer). Like so:
package com.fight.testpackages
public class EndpointList {
public static class PublicEndpoints implements ArgumentsProvider {
#Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of("/public/foo"),
);
}
}
public static class PrivateEndoints implements ArgumentsProvider {
#Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of("secret/bar"),
);
}
}
and then managed with tests like
#ParameterizedTest
#ArgumentsSource(PrivateContentEndpoints.class)
public void privateEndpoint_unauthorizedUser_isUnauthorizedResponse(String url) throws Exception {
assertFalse(super.isAuthenticated(url));
}
#WithMockUser(roles = "USER")
#ParameterizedTest
#ArgumentsSource(PublicAccountManagementEndpoints.class)
public void publicEndpoint_authorizedUser_hasAccess(String url) throws Exception {
assertTrue(super.isAuthenticated(url));
}
The issue I am trying to solve can best be described with the following scenario:
A developer adds a new endpoint;
They add the endpoint to list of antmatchers (if it should be public);
And then they add the endpoint to a list of public and private endpoints that gets pulled into the tests.
The problem here is that there is no enforcement of this behaviour, and it's super easy to forget to add an endpoint to a test, or if the names of the endpoints change then the tests need to be updated.
The current setup works, but I was wondering if there was a standard for this? I've looked at the #PreAuthorize and #RolesAllowed, etc but they only seem to be useful for securing a method, and not making it public. I actually want the reverse (i.e. the endpoint to be private by default, and then to be marked as publicly available intentionally).
A solution that I've come up with is as follows:
Create an annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface EndpointSecurity {
boolean isPublic() default false;
}
assign annotation to method if you want to make it public
#EndpointSecurity(isPublic = true)
#GetMapping("/public/foo")
String methodForEveryone() {
return "Hi common dude";
}
Build a scanner that checks all RestController methods for the EndpointSecurity annotation and the REST mapping annotation, kind of like below.. Hopefully it's enough to get the point..
#DependsOn("classScanner")
public class ClassMethodScanner {
private final List<Class<? extends Annotation>> annotationFilters;
private List<Method> annotatedMethods;
private final AnnotationScanner<?> classScanner;
public <T extends Annotation> ClassMethodScanner(ClassScanner<T> classScanner) {
this(classScanner, Collections.emptyList());
}
public <T extends Annotation> ClassMethodScanner(ClassScanner<T> classScanner,
List<Class<? extends Annotation>> annotations) {
this.classScanner = classScanner;
this.annotationFilters = annotations;
}
#PostConstruct
void extractAnnotatedMethods() throws ClassNotFoundException {
if (annotatedMethods == null) {
annotatedMethods =
classScanner.getAnnotatedHandlers().stream()
.sequential()
.map(Class::getDeclaredMethods)
.flatMap(Arrays::stream)
.filter(this::hasExpectedAnnotations)
.collect(Collectors.toUnmodifiableList());
}
}
private boolean hasExpectedAnnotations(Method method) {
return
(annotationFilters.isEmpty() && method.getAnnotations().length > 0)
|| annotationFilters.stream().anyMatch(method::isAnnotationPresent);
}
//Is there a good way of making this protected?
public List<Method> getAnnotatedMethods() {
return annotatedMethods;
}
}
And finally produce a list of public and private endpoints that feeds into the HttpSecurity.
public class SecurityEndpoints {
private List<String> publicEndpoints;
private List<String> privateEndpoints;
private final EndpointDetailsCollector<?> collector;
public String[] getWhiteList() {
And feeds into the EndpointList that I mentioned above.
This seems somewhat convoluted though. So was wondering what is a standard approach, or am I making too much of testing the endpoints??

Codename One - Externalizable object not supported in web service

I have created a CN1 web service which some custom objects that I want to externalize in order to send over the network. I read through several articles on how to create the web service and how to work with the CN1 Externalizable interface.
This works well for web service methods that return a custom externalizable object, however the only indicator that I have is that a method which takes an externalizable object as an argument, I get the following error:
SCHWERWIEGEND: Servlet.service() for servlet [CN1WebServiceServlet]
in context with path [/<myPath>] threw exception
java.io.IOException: Object type not supported: Post
The object is properly registered with the Util class, as changing either the object ID or commenting out the register call will cause a null pointer instead of the IO exception.
The Post class looks like this (simplified to the minimum which already fails):
public class Post implements Externalizable {
public int postid;
public int userid;
// default constructor needed for web service marshalling
public Post() {
}
#Override
public int getVersion() {
return 1;
}
#Override
public void externalize(DataOutputStream out) throws IOException {
Util.writeUTF("" + postid, out);
Util.writeUTF("" + userid, out);
}
#Override
public void internalize(int version, DataInputStream in) throws IOException {
this.postid = Integer.parseInt(Util.readUTF(in));
this.userid = Integer.parseInt(Util.readUTF(in));
}
#Override
public String getObjectId() {
return "Post";
}
Note that this Post object works well when I call a web service method which returns a post object, but not when I send a Post object to the web service:
// works
public static com.codename1.io.Externalizable getPostDetails(int postid) {
return getPostDetails(postid);
}
// fails
public static void sendPost(com.codename1.io.Externalizable post) {
sendPost(post);
}
I am at a loss of what I missed here.
Thanks and best regards
In your Servlet code call Util.register("Post", Post.class); which should hopefully resolve this.
Thanks a lot Shai! My mistake was to assume that registering the externalizable object on one side only. But of course it needs to be registered wherever it is going to be internalized, so in this case on my server.
Solution:
Within the "CN1WebServiceServlet" (not the ProxyServer class where the rest of the code has to be completed), call Util.register("Post", Post.class);
if(methodName.equals("sendPost")) {
Util.register("Post", Post.class); // this is a my insertedline, rest is generated
Object[] args = ProxyServerHelper.readMethodArguments(di, def_sendPost);
WebServiceProxyServer.sendPost((com.codename1.io.Externalizable)args[0]);
ProxyServerHelper.writeResponse(response, def_sendPost);
return;
}

Allow access to REST API only for mobile devices

It is possible to allow only mobile devices to access REST API on my server side (Based on spring framework) ?
I just found a solution, don't say that is the best way but anyway it works. For that I'm using spring-mobile library
Create a new Interceptor, to check device type and know to allow him to access API or not, code:
public class MobileDeviceInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Device d = new LiteDeviceResolver().resolveDevice(request);
if(!d.isNormal()) {
return true;
}
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
In configuration class add it:
public class AppConfig extends WebMvcConfigurerAdapter {
...
#Bean
public MobileDeviceInterceptor deviceTypeInterceptor(){
return new MobileDeviceInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(deviceTypeInterceptor());
}
}
Test it right now and it works.
Note You have to know if User-Agent headers was changed on non mobile platform into mobile values for example:
android
ipad
silk
...
This interceptor will allow access.

CXF Interceptor tied up to web method does not work

I need to catch exception of business logic and cut down its message (stack trace of exception, I don't want to log a bunch of stack trace, just exception type and its message). According to the examples in internet, I have to override or implement handleFault(Message message) method. I have web method for which I attached Interceptor class implements org.apache.cxf.interceptor.Interceptor. But It doesn't work. There is no sign of its call. I can't find any solution surfing through internet.
My web service :
#Stateless
#WebService(name = "LocationServicesWS", targetNamespace = "http://com.example.apps.sc.ws", serviceName = "locationServices")
#WebContext(contextRoot = "/bb/sc", urlPattern = "/lb", transportGuarantee = "NONE", secureWSDLAccess = false)
#Pool("sl-strict-pool")
public class LbServices implements IServicesLocal, IServicesRemote {
#WebMethod
#Override
#Interceptors(TestFaultInterceptor.class)
public LbLocation getLbsLocationService(#WebParam(name="ms")String ms, #WebParam(name="serID")Long ser) throws ScAccViolation, LbsException {
return serviceProcesses.getLbsLocationForService(ms, ser);
}
}
My custom interceptor:
#Interceptor
#Priority(Interceptor.Priority.APPLICATION)
public class TestFaultInterceptor implements org.apache.cxf.interceptor.Interceptor {
private static Logger logger = Logger.getLogger(String.valueOf(TestFaultInterceptor.class));
#Override
public void handleMessage(Message message) throws Fault {
//this method is not invoked
logger.info("handleMessage");
}
#Override
public void handleFault(Message message) {
//this method is also not invoked
logger.info("handleFault");
}
/*
#AroundInvoke
public Object intercept(InvocationContext invocationContext) throws Exception {
// This method works fine.
return invocationContext.proceed();
}
*/
But in my Interceptor class when I don't implement Interceptor and put annotation #AroundInvoke it works. So what's the problem ? Is there any solution without creating additional *.xml files ?
I have even put annotation
#Priority(Interceptor.Priority.APPLICATION)
But it's to no avail.
Here are links I stumbled in the internet (especially how to not log whole stack trace).
Apache CXF JAX-WS return fault without logging exception
How to get incoming & outgoing soap xml in a simple way using Apache CXF?

jersey 2: How to create custom HTTP param binding

I am trying to create a custom http param binding for my restful service. Please see the example below.
#POST
#Path("/user/{userId}/orders")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(#PathParam("userId") String someString, #UserAuthHeaderParam String authString){
}
You can see that there is a UserAuthHeaderParam annotation in the function signature. What I want to do is have a custom http param binding other than the standard javax.ws.rs.*Param .
I have try to implement org.glassfish.hk2.api.InjectionResolver which basically extract the value from http header:
public class ProtoInjectionResolver implements InjectionResolver<UserAuthHeaderParam>{
...
#Override
public Object resolve(Injectee injectee, ServiceHandle< ? > root)
{
return "Hello World";
}
...
}
When I call the restful service, the server get below exceptions. It indicates that the framework fails to resolve the param in the function signature:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=String,parent=MyResource,qualifiers={}),position=0,optional=false,self=false,unqualified=null,2136594195),
java.lang.IllegalArgumentException: While attempting to resolve the dependencies of rs.server.MyResource errors were found
Please help. Any advise is appreciated. I do make a lot of search on google but fails to make it work. Jersey 2.*. How to replace InjectableProvider and AbstractHttpContextInjectable of Jersey 1.* might be the similar question.
-- UPDATES:
I use AbstractBinder to bind my resolver to UserAuthHeaderParam:
public class MyApplication extends ResourceConfig
{
public MyApplication()
{
register(new AbstractBinder()
{
#Override
protected void configure()
{
// bindFactory(UrlStringFactory.class).to(String.class);
bind(UrlStringInjectResolver.class).to(new TypeLiteral<InjectionResolver<UrlInject>>()
{
}).in(Singleton.class);
}
});
packages("rs");
}
}
Thank you!
If all you want is to pass value directly from the header to the method you don't need to create custom annotations. Let's say you have a header Authorization, then you can easily access it by declaring your method like this:
#GET
public String authFromHeader(#HeaderParam("Authorization") String authorization) {
return "Header Value: " + authorization + "\n";
}
You can test it by calling curl, e.g.
$ curl --header "Authorization: 1234" http://localhost:8080/rest/resource
Header Value: 1234
Given that the answer to your question, how to create custom binding is as follows.
First you have to declare your annotation like this:
#java.lang.annotation.Target(PARAMETER)
#java.lang.annotation.Retention(RUNTIME)
#java.lang.annotation.Documented
public #interface UserAuthHeaderParam {
}
Having your annotation declared you have to define how it will be resolved. Declare the Value Factory Provider (this is where you'll have access to the header parameters - see my comment):
#Singleton
public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) {
return null;
}
return new AbstractHttpContextValueFactory<String>() {
#Override
protected String get(HttpContext httpContext) {
// you can get the header value here
return "testString";
}
};
}
}
Now declare an injection resolver
public class UserAuthHeaderParamResolver extends ParamInjectionResolver<UserAuthHeaderParam> {
public UserAuthHeaderParamResolver() {
super(UserAuthHeaderParamValueFactoryProvider.class);
}
}
and a Binder for your configuration
public class HeaderParamResolverBinder extends AbstractBinder {
#Override
protected void configure() {
bind(UserAuthHeaderParamValueFactoryProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(UserAuthHeaderParamResolver.class)
.to(new TypeLiteral<InjectionResolver<UserAuthHeaderParam>>() {})
.in(Singleton.class);
}
}
now the last thing, in your ResourceConfig add register(new HeaderParamResolverBinder()), like this
#ApplicationPath("rest")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(new HeaderParamResolverBinder());
packages("your.packages");
}
}
Given that, you should be now able to use the value as you wanted:
#GET
public String getResult(#UserAuthHeaderParam String param) {
return "RESULT: " + param;
}
I hope this helps.
I don't know how to resolve your exception. However, may I propose you a different way to do the same thing. I hope it helps.
I've faced exactly the same problem: I need extra parameters in the http header (btw, also related to authentication). Besides, I need to send them in every call, since I want to do a "typical" rest implementation, without maintaining a session.
I'm using Jersey 2.7 - but I'd say it should work in 2.0. I've followed their documentation
https://jersey.java.net/documentation/2.0/filters-and-interceptors.html
It's quite clear there, but anyway I copy-paste my implementation below.
It works fine. True there are some other ways to secure a rest service, for example this is a good one:
http://www.objecthunter.net/tinybo/blog/articles/89
But they depend on the application server implementation and the database you use. The filter, in my opinion, is more flexible and easier to implement.
The copy-paste: I've defined a filter for authentication, which applies to every call and it is executed before the service (thanks to #PreMatching).
#PreMatching
public class AuthenticationRequestFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
final MultivaluedMap<String, String> headers = requestContext.getHeaders();
if (headers == null) {
throw new...
}
// here I get parameters from the header, via headers.get("parameter_name")
// In particular, I get the profile, which I plan to use as a Jersey role
// then I authenticate
// finally, I inform the Principal and the role in the SecurityContext object, so that I can use #RolesAllowed later
requestContext.setSecurityContext(new SecurityContext() {
#Override
public boolean isUserInRole(final String arg0) {
//...
}
#Override
public boolean isSecure() {
//...
}
#Override
public Principal getUserPrincipal() {
//...
}
#Override
public String getAuthenticationScheme() {
//...
}
});
}
}
You have to include this filter class in your implementation of ResourceConfig,
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig() {
// my init
// my packages
register(AuthenticationRequestFilter.class); // filtro de autenticación
// other register
}
}
Hope it helps!
If your need is to retrieve all the http headers binding into one object, a solution could be to use the #Context annotation to get javax.ws.rs.core.HttpHeaders; which contains the list of all request headers.
#POST
#Path("/user/{userId}/orders")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(#PathParam("userId") String someString, #Context HttpHeaders headers){
// You can list all available HTTP request headers via following code :
for(String header : headers.getRequestHeaders().keySet()){
System.out.println(header);
}
}
here is my actual implementatipn of UserAuthHeaderParamValueFactoryProvider class
import javax.inject.Inject;
import javax.inject.Singleton;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.model.Parameter;
#Singleton
public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) {
return null;
}
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
//you can use get any header value.
return getContainerRequest().getHeaderString("Authorization");
}
};
}

Categories

Resources