I am using Http4s library to make HTTP calls to a REST web service. the rest web service requires me to set an authentication cookie.
I have written the following code to set this cookie.
val client = PooledHttp1Client()
val uri = Uri.uri("http://localhost/restService")
val req = Request(GET, uri)
req.headers.put(`Content-Type`(MediaType.`application/json`))
val cookie = org.http4s.Cookie("foo_session", getLoginSessionId, domain = Some("localhost"), path=Some("/"))
req.headers.put(org.http4s.headers.Cookie(cookie))
val task = client.expect[Response](req)
val list = task.run
list.response.foreach(println)
client.shutdownNow()
When I run this code I get a 401 error, meaning that the web service does not recognize that the cookie was set.
Now If I write the same code using apache http client. then everything works fine. The code below is doing exactly the same thing as above.
val get = new HttpGet(s"http://localhost/restService")
get.setHeader("Content-type", "application/json")
val client = new DefaultHttpClient()
val respHandler = new BasicResponseHandler
val cookieStore = new BasicCookieStore()
val cookie1 = new BasicClientCookie("foo_session", getLoginSessionId)
cookie1.setDomain("localhost")
cookie1.setPath("/")
cookieStore.addCookie(cookie1)
val localContext = new BasicHttpContext()
localContext.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore)
localContext
val responseString = client.execute(get, respHandler, cookieContext)
val list = parse(responseString).extract[Response]
list.response.foreach(println)
list.response
It seems that your cookie is not used into response.
Did you try to use the following approach :
val cookie = org.http4s.ResponseCookie("token", su.token.getOrElse("No token"), httpOnly = true, secure = true)
Ok("resp").map(_.addCookie(cookie))
if you want to attach the cookie server sent to your client, you can try Cookie Jar. https://github.com/http4s/http4s/blob/main/client/src/main/scala/org/http4s/client/middleware/CookieJar.scala
Http4s strictly check the cookie from server so chance are that some cookie entries are not replied. if it is the case, you may try this one:
https://github.com/chenharryhua/nanjin/blob/master/http/src/main/scala/com/github/chenharryhua/nanjin/http/client/middleware/package.scala#L39
Related
I try to execute a jenkins pipeline from a Scala app, but the authentication is required. So, I think before this request, I need to call the auth endpoint and get a token, then assign it on the headers.
val userToken = "user_token_generated_in_jenkins"
val pipelineToken = "pipeline_token"
val pipelineName = "HttpRequestTest"
// test1
val baseUrl = s"http://jenkins_address/view/Development/job/$pipelineName/build?token=$pipelineToken"
val response = Http(baseUrl).headers("token" -> "tried_a_valid_token_auth").asString
// test2
val url = s"http://auto:$userToken#jenkins_address/job/$pipelineName/build?token=$pipelineToken"
val response2 = Http(url).asString
If I can be sure that first test is wrong, why for test2, where I use the user token (generated in Users manager), it asks me to use extra token (generated by authentication)? Is a double verification and useless in my opinion.
As an emergency solution, I can execute the pipeline with curl using sys.process. But I prefer to solve it using Http library because I want to use generated token and not a specific username in my project code.
// test3
import sys.process._
val data = List("curl", "-u", "username:password", baseUrl).!!
How can I execute an HttpRequest using Jenkins User Token?
Thanks
If your Http(..) is from Akk-HTTP, the solution could be derived from here:
val authorization = headers.Authorization(BasicHttpCredentials("username", "password"))
HttpRequest(
PUT,
uri = "/user",
entity = HttpEntity(`text/plain` withCharset `UTF-8`, userData),
headers = List(authorization),
protocol = `HTTP/1.0`)
In case of scalaj-http it could be derived from here
With applying to your existing code, it should be like this:
Http(baseUrl).auth("username", "password").asString
By the way
I want to use generated token and not a specific username in my
project code
It is not a problem. You can construct your process' auth string dynamically. You are able to get the credentials from somewhere in the "native" Http solution, right? So you can construct your sys.process with the same getted-from-somewhere credentials by concatinating strings or string interpolation:
// test3
import sys.process._
val (username, password) = gettingFromSomewhereCreadentials()
val data = List("curl", "-u", s"$username:$password", baseUrl).!!
I'd like to test that a Http4s Client is being called from my class ClassUnderTest (to make a HTTP request) and that the request made contains the headers I expect.
In 0.18.x, I did something like below. Using a side effect to store the value of the headers so that I can make an assertion after the call. In the example below, f.execute(...) is expected to make a PUT with the client instance and I'm trying to record all request handling and storing the headers.
"Request has headers" >> {
var headers = List[String]()
val client = Client[IO](new Kleisli[IO, Request[IO], DisposableResponse[IO]](request => {
headers = request.headers.map(_.name.toString()).toList
Ok().map(DisposableResponse(_, IO.pure(())))
}), IO.pure(()))
val f = ClassUnderTest(client)
f.execute("example")
headers must_== List(
"Content-Type",
"X-Forwarded-For",
"Content-Length"
)
}
The real code is here if you're interested.
ClassUnderTest took a Client[IO] so I could get the above working.
class ClassUnderTest(client: http4s.Client[IO])
In Http4s 0.20.12, I had to change the signature to:
class ClassUnderTest(client: Resource[IO, http4s.Client[IO]])
...and now I can't figure out how to stub out the client for tests. I experimented with JavaNetClientBuilder but that doesn't help because I can get an instance of Client (after .create) and now I need a Resource[IO, http4s.Client[IO]].
How can I use a test double to stand in for the Client / Resource[F, Client[F]] so that I can test the requests it makes?
The testing page on the docs doesn't really help me. I want a test double, not to test all the functionality of the Service (I don't want to startup a server).
I am using akka-http for bulding a REST API. (I am new to build REST web services).
I don't know how I can get and set cookie without using a session. This cookie must contain encrypt token access. I don't use Play or spray.
My code for the moment is:
lazy val signin = path("signin") {
get {
/* create the OAuthService object with a callback URL*/
val service = buildService()
/* get the request token*/
val requestToken = service.getRequestToken
/* create the cookie */
val jwtCookieEncrypted = tokenUtil.createLinkedinTokenSecret(requestToken)
val cookie = HttpCookie("jwtTokenCookie", jwtCookieEncrypted)
/* making the user validate our requestToken by redirecting him to the following URL*/
val authURL = service.getAuthorizationUrl(requestToken)
redirect(authURL, StatusCodes.TemporaryRedirect)
}
}
lazy val callback = path("callback") {
// extract cookie with the jwtTokenCookie name
cookie("jwtTokenCookie") { cookiePair =>
complete(s"The logged in user is '${cookiePair.name}'")
}
get {
parameters('code, 'state) { (code, state) => // must come from cookie and not request parameters
/* create the OAuthService object with a callback URL*/
val service = buildService()
/* get the request token*/
val requestToken = new Token(code, state)
if(state == tokenUtil.decryptLinkedinToken(requestToken.getSecret).getOrElse("")) "continue" else "throw error"
val verifier = new Verifier(state)
/* get the access token
(need to exchange requestToken and verifier for an accessToken which is the one used to sign requests)*/
val accessToken = service.getAccessToken(requestToken, verifier)
logger.debug(accessToken.getRawResponse)
/* sign request*/
val ResourceUrl = Settings.LinkedIn.ResourceUrl
val request = new OAuthRequest(Verb.GET, ResourceUrl)
service.signRequest(accessToken, request)
val response = request.send
if (response.getCode == StatusCodes.OK.intValue) complete(response.getBody)
else complete(int2StatusCode(response.getCode))
}
}
}
signin ~ callback
Check the akka doc. In your response you can include the header. In your case, maybe with redirect it´s not so simple. But you could complete the signing request returning a 308 Http code with the Location Header pointing to your oauth2 Auth server.
Is it better ?
path("signin") {
get {
val service = buildService()
val requestToken = service.getRequestToken
val authURL = service.getAuthorizationUrl(requestToken)
val requestTokenCrypted = tokenUtil.createLinkedinToken(requestToken)
val cookie = HttpCookie("abcde", requestTokenCrypted.getSecret)
setCookie(cookie) {
complete(HttpResponse(
status = StatusCodes.TemporaryRedirect,
headers = List(Location(authURL))
))
}
}
}
This is the scenario I'm trying to achieve in Play 2.5.x for Scala (all the requests and responses are Json):
Browser sends HTTP Request to URL1.
URL1 enriches the Json it receives with some data, and forwards the entire request to URL2.
URL2 responds to browser.
In the last point, I'm not sure if URL2 can send it back to the browser or has to do it through URL1 (I believe it's the latter).
This is the request in URL1 (URL2 is a simple request/response):
val request: WSRequest = ws.url("/url2")
val request2: WSRequest = request.withHeaders("Accept" -> "application/json")
val data = Json.obj(
"aaa" -> some_data1,
"bbb" -> some_data2
)
val futureResponse: Future[JsValue] = request2.post(data).map {
response => response.json
}
When I send the future I get this exception:
Execution exception[[NullPointerException: scheme]]
How to fix this?
The clue is in the function name - it's ws.url, not ws.uri. You need to specify a complete path. You can use ws.url("http://localhost:9000/url2"), with customised elements if necessary such as the scheme and the port based on your configuration.
I know I can set cookies in Ok(...).withCookies(...) when returning an action. However I wonder if there is a way to set some cookies by manipulating the request object. So that I can set some cookies in my models and my controller just need to send them back.
I'm doing this only as exercise, and also to show that Play framework is very flexible and it doesn't limit you in any sense. I figured out how to do this purely from Play source code, it is very clean and easy to read. This is NOT the preferred way to work with cookies or indeed with HttpRequest object in Play. As Jatin suggested you should decode your cookies to proper models, pass those models to your services and then convert result of your services to play.api.mvc.Result, thus keeping your http and business logic layers separated.
Here's the code( you can see that Headers object is not intended to be used this way):
import play.api.http.HeaderNames.COOKIE
val cookies = Cookies(request.headers.get(COOKIE)).cookies
val myCookies = cookies + ("cookieName" -> Cookie("cookieName", "cookieValue"))
val headersMap = request.headers.toMap
val myHeaderMap = headersMap +
(COOKIE -> Seq(Cookies.encode(myCookies.values.toSeq)))
val myHeaders = new play.api.mvc.Headers {
val data:Seq[(String, Seq[String])] = myHeaderMap.toSeq
}
val modifiedRequest = request.copy(headers = myHeaders)