vertx how to reroute with query params - vert.x

Due to some url versioning, we try to map multiple paths to the same handler.
I tried to achieve this via rerouting but the query parameters get lost in the process.
// reroute if the path contains apiv3 / api v3
router.route("/apiv3/*").handler( context -> {
String path = context.request().path();
path = path.replace("apiv3/", "");
LOG.info("Path changed to {}", path);
context.reroute(path);
});
What is the most elegant way around this problem?
There are some discussions on google groups but surprisingly nothing quick and simple to implement.

The reroute documentation says:
It should be clear that reroute works on paths, so if you need to
preserve and or add state across reroutes, one should use the
RoutingContext object.
So you could create a global catch-all route that stores any query param in the RoutingContext:
router.route().handler(ctx -> {
ctx.put("queryParams", ctx.queryParams());
ctx.next();
});
Then your apiv3 catch-all route:
router.route("/apiv3/*").handler( context -> {
String path = context.request().path();
path = path.replace("apiv3/", "");
LOG.info("Path changed to {}", path);
context.reroute(path);
});
Finally an actual route handler:
router.get("/products").handler(rc -> {
MultiMap queryParams = rc.get("queryParams");
// handle request
});

Related

Identityserver4 with ComponentSpace SAML 2 get custom parameters during request

I am using IdentityServer4 with two external Idp's, one with WSFederation (ADFS) and one with SAML.
For the SAML implementation I use the commercial product ComponentSpace SAML 2 for ASP.Net Core. I use the middleware-based config.
Logging it with both Idp's works perfectly, but now I have the situation where, depending on the client, I need to pass extra parameters to the SAML AuthnRequest. I know how to pass this extra parameter in the request (I can use the OnAuthnRequestCreated from the middleware), but what I don't know is how to test at that point from where the request is coming, i.e. from which client.
I have control of the client so I could also pass extra acr_values (which I think can be used to pass custom data), but again I don't know how to get them in the OnAuthnRequestCreated event as shown in the code below.
Any help would be much appreciated.
services.AddSaml(Configuration.GetSection("SAML"));
services.AddAuthentication()
.AddWsFederation("adfs", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
//...rest of config (SSO is working)
})
.AddSaml("saml", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
//...rest of config (SSO is working)
options.OnAuthnRequestCreated = request =>
{
//Here I would need to know from which client the request is coming (either by client name or url or acr_values or whatever)
//to be able to perform conditional logic. I've checked on the request object itself but the info is not in there
return request;
};
});
The request parameter is the SAML AuthnRequest object. It doesn't include client information etc.
Instead of the OnAuthnRequestCreated event, in your Startup class you can add some middleware as shown below. You can call GetRequiredService to access any additional interfaces (eg IHttpContextAccessor) you need to retrieve the client information.
app.Use((context, next) =>
{
var samlServiceProvider =
context.RequestServices.GetRequiredService<ISamlServiceProvider>();
samlServiceProvider.OnAuthnRequestCreated += authnRequest =>
{
// Update authn request as required.
return authnRequest;
};
return next();
});
Thanks ComponentSpace for the reply. I didn't get it to work directly with your solution by using app.Use((context, next)) => ... but your comment on GetRequiredService pointed me into the direction to find the solution like below. Basically I'm getting the IHttpContextAccessor which I can then use to parse the query string. I then get the ReturnUrl from this query string and use the IIdentityServerInteractionService to get the AuthorizationContext object, which contains what I need to build my custom logic.
So thanks again for pointing me into the right direction.
//build and intermediate service provider so we can get already configured services further down this method
var sp = services.BuildServiceProvider();
services.AddAuthentication()
.AddSaml("SamlIdp", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.OnAuthnRequestCreated = request =>
{
var httpContextAccessor = sp.GetService<IHttpContextAccessor>();
var queryStringValues = HttpUtility.ParseQueryString(httpContextAccessor.HttpContext.Request.QueryString.Value);
var interactionService = sp.GetService<IIdentityServerInteractionService>();
var authContext = interactionService.GetAuthorizationContextAsync(queryStringValues["ReturnUrl"]).Result;
//authContext now contains client info and other useful stuff to help build further logic to customize the request
return request;
};
});

Send two params in GET request

I'm pretty new using vertx framework, and in the documentation I cannot see the silly thing about how to send two parameters in a GET request. So far I tried this.
$.getJSON('/user/'+ attributeName + ":"+value, function (data) {
userListData = data;
$.each(data, function () {
$('#userInfoName').text(data.fullname);
$('#userInfoAge').text(data.age);
$('#userInfoGender').text(data.gender);
$('#userInfoLocation').text(data.location);
});
});
And then in server side
router.get("/user/:attributeName:value").handler(routingContext -> {
JsonObject query = new JsonObject();
query.put(routingContext.request().getParam("attributeName"), routingContext.request().getParam("value"));
But then I can see how attributeName not only gets the value of the first param but part of the second, very weird.
You're probably doing it wrong. You can get it as a single param and later split with ":" or have two parameters defined in the url as... /:attribname/:value/...
Both will handle your requirement

Google sites API, IllegalArgumentException("Trying to set foreign cookie") after RedirectRequiredException

I am using the gdata-media-1.0-1.47.1.jar functionality to fetch media data using the com.google.gdata.client.media.MediaService.getMedia(IMediaContent mediaContent) method. For some requests I get a RedirectRequiredException. When I redo the getMedia request, using the url i get from RedirectRequiredException.getRedirectLocation(), I get an IllegalArgumentException("Trying to set foreign cookie") exception.
From what I can see, the reason for this is that the domain in the response header for the cookie doesn't match the domain of the redirect location. In com.google.gdata.client.http.GoogleGDataRequest.matchDomain() the first argument is ".docs.google.com" and the second is "docs.google.com" which makes the domain matching fail.
Is this a correct behaviour? Why is this happening? Is there something I can do about this? Am I doing anything wrong here? Is is possible to avoid this problem?
SitesService sitesService = new SitesService("SomeAppName");
try {
MediaContent mc = new MediaContent();
mc.setUri(aURI);
return sitesService.getMedia(mc);
} catch (RedirectRequiredException e) {
MediaContent mc = new MediaContent();
mc.setUri(e.getRedirectLocation());
return sitesService.getMedia(mc);
}

Make ember-data use the URL that ember is loading for the REST request

On the system I am working on right now, I have had to try to tame ember-data a bit about how it does its REST request. The way that ember-data by default figures the URL for a certain request for a model is just not gonna cut it with the backend I am using.
What I need is, to get ember-data to use the same URL that ember is loading, but with a '?json' suffix. That is, if ember switches page to my band page, and the url is /bands, I want ember-data to request /bands?json for the data it needs, not whatever it figures from the name of the model. One could say, that I wanted the URL to be calculated from the path of the loading route, instead of from the name of the model being used.
I have tried by subclassing DS.RESTAdapter{} and see if I could get the buildURL method to do this, but I can't figure out how to get the URL ember is gonna load. The buildURL method is called before ember changes the location, so I can't use document.location.href or something. I can imagine I will need a way to ask ember what it is now loading, and what the URL is.
Any ideas of how to do this?
UPDATE
There hasn't been any satisfying solutions, so I decided to just do it the dirty way. This is it:
App.RouterSignature = [
['index', '/', '/index_models'],
['bands', '/bands', '/band_models'],
['band', '/band/:band_slug', '/band_model']
];
App.Router.map(function() {
for (var i = 0; i < App.RouterSignature.length; i++) {
var route = App.RouterSignature[i];
this.resource(route[0], {path: route[1]});
}
});
App.CustomAdapter = DS.RESTAdapter.extend({
buildURL: function(record, suffix) {
var url,
suffix = '?json',
needle = this._super(record);
for (var i = 0; i < App.RouterSignature.length && !url; i++) {
var route = App.RouterSignature[i];
if (route[2] == needle)
url = route[1];
}
return url + suffix;
}
});
Now App.Routes and DS.RESTAdapter.buildURL are based off the same data. The first two values in the App.RouterSignature list is just the name of the route, the path of the route. The third value is what DS.RESTAdapter.buildURL by default guesses should be the url. My custom adapter then takes that guess, matches it with one of the items in the App.RouterSignature list and then takes the second value from that item - the routes path.
Now the requests that ember-data makes is to the same url as the routes path.
You can try to setup your Adapter like so:
App.Adapter = DS.RESTAdapter.extend({
...
buildURL: function(record, suffix){
return this._super(record, suffix) + "?json";
}
});
App.Store = DS.Store.extend({
...
adapter: App.Adapter.create();
...
});
See here for more info on the RESTAdapter buildURL method.
Hope it helps

How to construct a REST API that takes an array of id's for the resources

I am building a REST API for my project. The API for getting a given user's INFO is:
api.com/users/[USER-ID]
I would like to also allow the client to pass in a list of user IDs. How can I construct the API so that it is RESTful and takes in a list of user ID's?
If you are passing all your parameters on the URL, then probably comma separated values would be the best choice. Then you would have an URL template like the following:
api.com/users?id=id1,id2,id3,id4,id5
api.com/users?id=id1,id2,id3,id4,id5
api.com/users?ids[]=id1&ids[]=id2&ids[]=id3&ids[]=id4&ids[]=id5
IMO, above calls does not looks RESTful, however these are quick and efficient workaround (y). But length of the URL is limited by webserver, eg tomcat.
RESTful attempt:
POST http://example.com/api/batchtask
[
{
method : "GET",
headers : [..],
url : "/users/id1"
},
{
method : "GET",
headers : [..],
url : "/users/id2"
}
]
Server will reply URI of newly created batchtask resource.
201 Created
Location: "http://example.com/api/batchtask/1254"
Now client can fetch batch response or task progress by polling
GET http://example.com/api/batchtask/1254
This is how others attempted to solve this issue:
Google Drive
Facebook
Microsoft
Subbu Allamaraju
I find another way of doing the same thing by using #PathParam. Here is the code sample.
#GET
#Path("data/xml/{Ids}")
#Produces("application/xml")
public Object getData(#PathParam("zrssIds") String Ids)
{
System.out.println("zrssIds = " + Ids);
//Here you need to use String tokenizer to make the array from the string.
}
Call the service by using following url.
http://localhost:8080/MyServices/resources/cm/data/xml/12,13,56,76
where
http://localhost:8080/[War File Name]/[Servlet Mapping]/[Class Path]/data/xml/12,13,56,76
As much as I prefer this approach:-
api.com/users?id=id1,id2,id3,id4,id5
The correct way is
api.com/users?ids[]=id1&ids[]=id2&ids[]=id3&ids[]=id4&ids[]=id5
or
api.com/users?ids=id1&ids=id2&ids=id3&ids=id4&ids=id5
This is how rack does it. This is how php does it. This is how node does it as well...
There seems to be a few ways to achieve this. I'd like to offer how I solve it:
GET /users/<id>[,id,...]
It does have limitation on the amount of ids that can be specified because of URI-length limits - which I find a good thing as to avoid abuse of the endpoint.
I prefer to use path parameters for IDs and keep querystring params dedicated to filters. It maintains RESTful-ness by ensuring the document responding at the URI can still be considered a resource and could still be cached (although there are some hoops to jump to cache it effectively).
I'm interested in comments in my hunt for the ideal solution to this form :)
You can build a Rest API or a restful project using ASP.NET MVC and return data as a JSON.
An example controller function would be:
public JsonpResult GetUsers(string userIds)
{
var values = JsonConvert.DeserializeObject<List<int>>(userIds);
var users = _userRepository.GetAllUsersByIds(userIds);
var collection = users.Select(user => new { id = user.Id, fullname = user.FirstName +" "+ user.LastName });
var result = new { users = collection };
return this.Jsonp(result);
}
public IQueryable<User> GetAllUsersByIds(List<int> ids)
{
return _db.Users.Where(c=> ids.Contains(c.Id));
}
Then you just call the GetUsers function via a regular AJAX function supplying the array of Ids(in this case I am using jQuery stringify to send the array as string and dematerialize it back in the controller but you can just send the array of ints and receive it as an array of int's in the controller). I've build an entire Restful API using ASP.NET MVC that returns the data as cross domain json and that can be used from any app. That of course if you can use ASP.NET MVC.
function GetUsers()
{
var link = '<%= ResolveUrl("~")%>users?callback=?';
var userIds = [];
$('#multiselect :selected').each(function (i, selected) {
userIds[i] = $(selected).val();
});
$.ajax({
url: link,
traditional: true,
data: { 'userIds': JSON.stringify(userIds) },
dataType: "jsonp",
jsonpCallback: "refreshUsers"
});
}