convenience method in spray (soon to be akka-http) to create a Location header w/ host port & contextRoot ? - scala

When I create an object through POST in my Spray app I'd like to return a 201 status, together with a Location header that has the absolute URI of the newly created resource (including host port & contextRoot of my app)
Here is an example code fragment from my application...
post {
respondWithHeaders(Location( fullyQualifiedUri("/movies"))) {
entity(as[MovieImpl]) { (movieToInsert: MovieImpl) => {
addMovies(movieToInsert)
complete("OK")
}
}
}
}
Note that I now have to write the method 'fullyQualifiedUri' to return
a URI with host, port, etc. It would be nice if Spray did that for me
automagically.
Side note:
I think that having the Location header include the absolute URI of the newly
created resource makes it easier on my REST API's clients (although it does seem that there are a variety of opinions on this.)
Thanks in advance for any guidance you can provide.
-chris

To build the URI you'll need the Id of the newly created resource. Then you can use the requestInstance directive to get the incoming request URI and build the new resource URI from it. You also need to set the return code to Created to meet your requirements:
post {
requestInstance { request =>
val movieId = ???
respondWithHeaders(Location( request.uri.withPath(request.uri.path / movieId))) {
entity(as[MovieImpl]) { (movieToInsert: MovieImpl) => {
addMovies(movieToInsert)
complete(StatusCodes.Created)
}
}
}
}
}

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;
};
});

Getting wslite.http.HTTPClientException: 404 Not Found. Verified the Soap Address is correct

I am using groovy wslite plugin to make a soap call. I am getting wslite.http.HTTPClientException: 404 Not Found. Can someone help me to debug the issue.
Groovy code
def client = new SOAPClient(Helper.getConfigValue('grails.soap.ws.url'))
try {
def response = client.send(SOAPAction: SOAP_NAMESPACE + SOAP_ACTION_START_PROCESS,
sslTrustAllCerts: true) {
header {
delegate.securityInfo(buildSecurityInfoObject())
}
body {
getNotificationRequest('xmlns': SOAP_NAMESPACE) {
delegate.transactionId('12345')
}
}
}
You probably did this alredy, here I ask anyway: Have you verified that the resource exists under the path/url you are trying to retrieve it from?
404 often indicate that the host is not available.

How can I redirect all unknown URLs in lift framework?

I'm not very familiar with lift framework and wanted to know if the following use case is possible using lift framework.
On server1, Lift is serving REST webservice at following url "/contact/"
However, if the client sends request to the following URL https://server1/contact/meet/" then it is not implemented on this specific server but "might" be implemented by another server. Can Lift redirect any such unsupported URLs to some specific server? Eg, in 302 response, can Location be specified by Lift to https://server2/contact/meet/ ?
Please note that these are unknown URLs and can't be configured statically.
Yeah, I get it. Maybe you need LiftRules.dispatch and net.liftweb.http.DoRedirectResponse. Following is the code I try to solve your trouble.
// The code should in the server1; JsonDSL will be used by JsonResponse
class Boot extends Bootable with JsonDSL {
def boot {
initDispatch
}
def initDispatch {
LiftRules.dispatch.append {
case Req("contact" :: url :: Nil, _, GetRequest) => {
() => Full(
if (url == "join") {
// or other url that match what will be implemented in server1
// your implementation, say JsonResponse
JsonResponse("server1" -> true)
} else {
// if the url part does not match simply redirect to server2,
// then you have to deal with how to process the url in server2
DoRedirectResponse("https://server2/contact/meet/")
}
)
}
}
}
}
Anyway, hope it helps.

Spray Authentication method with BasicAuth

Spray is hard!! I now know that my knowledge on HTTP protocol is not nearly enough and API design isn't easy. However, I still very much want my practice app to work. I'm writing this authentication for POST/PUT/DELETE method. It appears that there are at least two ways to do this: BasicAuth or write a custom directive.
I found this article:
BasicAuth: https://github.com/jacobus/s4/blob/master/src/main/scala/s4/rest/S4Service.scala
I'm trying it out because it looks simple.
The compile and run stages are fine, and the server runs. However, when I'm trying to send a PUT request to test the implementation (using Python's Httpie: http PUT 127.0.0.1:8080/sec-company/casper username=username token=123), the feedback is:HTTP/1.1 404 Not Found
Here's my route:
pathPrefix("sec-company") {
path("casper") {
//first examine username and token
authenticate(BasicAuth(CustomUserPassAuthenticator, "company-security")) {userProfile =>
post { ctx =>
entity(as[Company.Company]) { company =>
complete {
company
}
}
}
}
Here is my implementation of UserPassAuthenticator:
object CustomUserPassAuthenticator extends UserPassAuthenticator[UserProfile] {
def apply(userPass: Option[UserPass]) = Promise.successful(
userPass match {
case Some(UserPass(user, token)) => getUserProfile(user, token)
case _ => None
}
).future
}
First of all, is this the right way to implement authentication? Second, where does UserPassAuthenticator find the username and password?? Can I send back a better HTTP header other than 404 to indicate failed authentication?
If this is far from correct, is there any tutorial on authentication that I can follow? TypeSafe's Spray templates are more about overall patterns and less about Spray's functionality!
Thank you!
I had the same problem, even after looking at https://github.com/spray/spray/wiki/Authentication-Authorization (which says it's for an older version of Akka but it still seems to apply) I came up with the following:
trait Authenticator {
def basicUserAuthenticator(implicit ec: ExecutionContext): AuthMagnet[AuthInfo] = {
def validateUser(userPass: Option[UserPass]): Option[AuthInfo] = {
for {
p <- userPass
user <- Repository.apiUsers(p.user)
if user.passwordMatches(p.pass)
} yield AuthInfo(user)
}
def authenticator(userPass: Option[UserPass]): Future[Option[AuthInfo]] = Future { validateUser(userPass) }
BasicAuth(authenticator _, realm = "Private API")
}
}
I mix in this trait into the Actor that runs the routes and then I call it like this:
runRoute(
pathPrefix("api") {
authenticate(basicUserAuthenticator) { authInfo =>
path("private") {
get {
authorize(authInfo.hasPermission("get") {
// ... and so on and so forth
}
}
}
}
}
}
The AuthInfo object returned by the validateUser method is passed as a parameter to the closure given to the authorize method. Here it is:
case class AuthInfo(user: ApiUser) {
def hasPermission(permission: String) = user.hasPermission(permission)
}
In Spray (and HTTP), authentication (determining whether you have a valid user) is separate from authorization (determining whether the user has access to a resource). In the ApiUser class I also store the set of permissions the user has. This is a simplified version, my hasPermission method is a bit more complex since I also parametrize permissions, so it's not just that a particular user has permission to do a get on a resource, he might have permission to read only some parts of that resource. You might make things very simple (any logged-in user can access any resource) or extremely complex, depending on your needs.
As to your question, when using HTTP BASIC authentication (the BasicAuth object), the credentials are passed in the request in an Authorization: header. Your HTTP library should take care of generating that for you. According to the HTTP standard, the server should return a 401 status code if the authentication was incorrect or not provided, or a 403 status code if the authentication was correct but the user doesn't have permissions to view the content.

How to view TCL REST Response headers

My code sample was
package require rest
set yweather(forecast) {
url http://weather.yahooapis.com/forecastrss
req_args { p: }
opt_args { u: }
}
rest::create_interface yweather
Output
% set res [yweather::forecast -p 94089]
channel {title {content {Yahoo! Weather - Sunnyvale, CA}} .........
But i am trying to view Response header, like Status codes, set-cookie information. I don't know how to view, some one please help to resolve this issue.
Thanks
Typically with handling REST, I'd just use the standard http package directly (or wrapped into a little class). That would let you use http::meta to get at the gory response details, and also let you control much more precisely what message gets sent in the first place (usually rather important!)
However, that's me (as I'm pretty au fait with REST and the http package). Let's dig into the rest package more carefully and get it to do what we want.
By close reading of the documentation, I see that the interface descriptor dictionary allows the keys pre_transform and post_transform, and that the http token is available in the calling context. Let's try with the post_transform…
package require rest
set yweather(forecast) {
url http://weather.yahooapis.com/forecastrss
req_args { p: }
opt_args { u: }
post_transform extract_metadata
}
rest::create_interface yweather
proc extract_metadata {response} {
upvar 1 token token
lappend response [http::meta $token]
return $response
}
Now, if you do:
set res [yweather::forecast -p 94089]
You should get the extra information you want (and far more probably!) in the meta field at the end.