Unable to perform successful Paypal webhook validation - paypal

I am working to validate Paypal webhook data but I'm running into an issue where it's always returning a FAILURE for the validation status. I'm wondering if it's because this is all happening in a sandbox environment and Paypal doesn't allow verification for sandbox webhook events? I followed this API doc to implement the call: https://developer.paypal.com/docs/api/webhooks/v1/#verify-webhook-signature
Relevant code (from separate elixir modules):
def call(conn, _opts) do
conn
|> extract_webhook_signature(conn.params)
|> webhook_signature_valid?()
|> # handle the result
end
defp extract_webhook_signature(conn, params) do
%{
auth_algo: get_req_header(conn, "paypal-auth-algo") |> Enum.at(0, ""),
cert_url: get_req_header(conn, "paypal-cert-url") |> Enum.at(0, ""),
transmission_id: get_req_header(conn, "paypal-transmission-id") |> Enum.at(0, ""),
transmission_sig: get_req_header(conn, "paypal-transmission-sig") |> Enum.at(0, ""),
transmission_time: get_req_header(conn, "paypal-transmission-time") |> Enum.at(0, ""),
webhook_id: get_webhook_id(),
webhook_event: params
}
end
def webhook_signature_valid?(signature) do
body = Jason.encode!(signature)
case Request.post("/v1/notifications/verify-webhook-signature", body) do
{:ok, %{verification_status: "SUCCESS"}} -> true
_ -> false
end
end
I get back a 200 from Paypal, which means that Paypal got my request and was able to properly parse it and run it though its validation, but it's always returning a FAILURE for the validation status, meaning that the authenticity of the request couldn't be verified. I looked at the data I was posting to their endpoint and it all looks correct, but for some reason it isn't validating. I put the JSON that I posted to the API (from extract_webhook_signature) into a Pastebin here cause it's pretty large: https://pastebin.com/SYBT7muv
If anyone has experience with this and knows why it could be failing, I'd love to hear.

I solved my own problem. Paypal does not canonicalize their webhook validation requests. When you receive the POST from Paypal, do NOT parse the request body before you go to send it back to them in the verification call. If your webhook_event is any different (even if the fields are in a different order), the event will be considered invalid and you will receive back a FAILURE. You must read the raw POST body and post that exact data back to Paypal in your webhook_event.
Example:
if you receive {"a":1,"b":2} and you post back {..., "webhook_event":{"b":2,"a":1}, ...} (notice the difference in order of the json fields from what we recieved and what we posted back) you will recieve a FAILURE. Your post needs to be {..., "webhook_event":{"a":1,"b":2}, ...}

For those who are struggling with this, I'd like to give you my solution which includes the accepted answer.
Before you start, make sure to store the raw_body in your conn, as described in Verifying the webhook - the client side
#verification_url "https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature"
#auth_token_url "https://api-m.sandbox.paypal.com/v1/oauth2/token"
defp get_auth_token do
headers = [
Accept: "application/json",
"Accept-Language": "en_US"
]
client_id = Application.get_env(:my_app, :paypal)[:client_id]
client_secret = Application.get_env(:my_app, :paypal)[:client_secret]
options = [
hackney: [basic_auth: {client_id, client_secret}]
]
body = "grant_type=client_credentials"
case HTTPoison.post(#auth_token_url, body, headers, options) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
%{"access_token" => access_token} = Jason.decode!(body)
{:ok, access_token}
error ->
Logger.error(inspect(error))
{:error, :no_access_token}
end
end
defp verify_event(conn, auth_token, raw_body) do
headers = [
"Content-Type": "application/json",
Authorization: "Bearer #{auth_token}"
]
body =
%{
transmission_id: get_header(conn, "paypal-transmission-id"),
transmission_time: get_header(conn, "paypal-transmission-time"),
cert_url: get_header(conn, "paypal-cert-url"),
auth_algo: get_header(conn, "paypal-auth-algo"),
transmission_sig: get_header(conn, "paypal-transmission-sig"),
webhook_id: Application.get_env(:papervault, :paypal)[:webhook_id],
webhook_event: "raw_body"
}
|> Jason.encode!()
|> String.replace("\"raw_body\"", raw_body)
with {:ok, %{status_code: 200, body: encoded_body}} <-
HTTPoison.post(#verification_url, body, headers),
{:ok, %{"verification_status" => "SUCCESS"}} <- Jason.decode(encoded_body) do
:ok
else
error ->
Logger.error(inspect(error))
{:error, :not_verified}
end
end
defp get_header(conn, key) do
conn |> get_req_header(key) |> List.first()
end

Related

Gatling - Scala : How to repeat a request until a certain response variable exists in the API response?

Gatling - Scala : How to repeat a request until a certain response variable exists in the API response?
This is the request to find the response time of a cursor pagination API
.exec(http("APITests:Cursor Pagination")
.get("/testapi")
.queryParam("sortField", "ID")
.queryParam("limit", limitCount)
.queryParam("cursor", "#{CursorID}")
.check(jsonPath("$.nextCursor")).exists
.check(status.is(200))
)
I have to repeat the request execution until .check(jsonPath("$.nextCursor")).exists = False
Please provide suggestions and help
I tried below ending with error:
doWhile(session => session(".check(jsonPath(\"$.nextCursor\").exists").as[Boolean]) {
exec(http("APITests:Cursor Pagination")
.get("/testapi")
.queryParam("sortField", "ID")
.queryParam("limit", limitCount)
.queryParam("cursor", "#{CursorID}")
.check(status.is(200))
.check(jsonPath("$.nextCursor").exists
))
}
But I end up with error :
jsonPath($.nextCursor).find.exists, found nothing
Use contains, but you have to save nextCursor into Session
doWhile(session => !session.contains("nextCursor")) {
exec(http("...")
.get("/")
.check(jsonPath("$.nextCursor").saveAs("nextCursor"))
)
}

Get token id from Login request in Gatling

I am using Gatling for performance testing, so I want know that how we extract token id from the login request here is code
val scn = scenario("Navigation")
.exec(http("request_6")
.post("/WEBAUTO03/aurora/login/security_check")
.headers(headers_6)
.formParam("j_username", "TONY")
.formParam("j_password", "1234")
.formParam("doLogin", "")
Above request provide token and I need apply the token in following request
val headers_9 = Map(
"Content-type" -> "text/plain",
"Origin" -> "https://resource.com",
"X-XSRF-TOKEN" -> ""4c81ed9c-e509-4830-b724-62e489c918e2"") -----here i need to replace token
.exec(http("request_9")
.post("/WEBAUTO03/aurora/JSON-RPC")
.headers(headers_9)
.body(RawFileBody("webview/navigation/0009_request.txt")))
anyone have any idea
Without seeing the response from the "above request" we cannot suggest the exact steps, approximate would be something like:
http("request_6")
.post("/WEBAUTO03/aurora/login/security_check")
.check(css("input[name='csrf_token']", "value").saveAs("Correlation1"))
val headers_9 = Map(
"Content-type" -> "text/plain",
"Origin" -> "https://resource.com",
"X-CSRF-Token" -> "${Correlation1}")
More information:
Gatling HTTP Checks
How to Run a Simple Load Test with Gatling

i'm unable to get list of all the users who joined channel through presence in elixir, phoenix

this is a quiz channel and after joining I want to get all the users who joined all the quizzes
quiz channel
def join("quiz:" <> id, _params, socket) do
presence = Nuton.Presence.list("quiz:" <> id)
if presence == %{} do
send(self(), {:after_join_quiz, id})
response = %{message: "you can now listen"}
{:ok, response, socket}
else
quiz = "quiz:#{id}"
%{^quiz => %{metas: metas}} = presence
if Enum.count(metas) > 1 do
{:error, %{reason: "Some other user already accepted the invitation"}}
else
send(self(), {:after_join_quiz, id})
response = %{message: "you can now listen"}
:ok = ChannelWatcher.monitor(:quiz, self(), {__MODULE__, :leave, [id]})
{:ok, response, socket}
end
end
end
def handle_info({:after_join_quiz, id}, socket) do
presence = Presence.list(socket)
if presence == %{} do
{:ok, _} =
Presence.track(socket, "quiz:" <> id, %{
user_name: socket.assigns.current_user.username,
user_id: socket.assigns.current_user.id,
quiz_id: id
})
{:noreply, socket}
else
{:ok, _} =
Presence.track(socket, "quiz:" <> id, %{
user_name: socket.assigns.current_user.username,
user_id: socket.assigns.current_user.id,
quiz_id: id
})
Core.Endpoint.broadcast(
"user:all",
"invitation:decline",
%{message: "Some other user already accepted the invitation"}
)
{:noreply, socket}
end
end
with specific quiz_id I can get all the user who joined the channel but with all I cant is there any issue in my code plz check if it is
Controller
quiz_users = Nuton.Presence.list("quiz:all")
You'd need to cycle through all of the channels, somehow, to get that information. I believe there was a PR to get this info, before, actually, so you can cycle through all.
What would be better: if you have the socket join 2 channels, like a "lobby" or "all" channel and then each individual channel. When you track, you can specify the string for the topic instead of only putting in the socket:
track(socket, "quiz:all", socket.assigns.user.id,%{})

Elixir Phoenix Swagger Security Definitions

I have integrated phoenix_swagger into my backend. I am autogenerating my swagger doc UI based off my controllers and using it to interactively test my endpoints.
Nonetheless, my routes are secured with Bearer JWTs. I am trying to figure out how to define authorization headers in phoenix_swagger with absolutely no luck.
I really appreciate the help Elixir friends!
For a visual:
swagger_path :create_user do
post "/api/v1/users/create"
description "Create a user."
parameters do
user :body, Schema.ref(:Create), "User to save", required: true
end
response 200, "Success"
end
def create_user(conn, query_params) do
changeset = User.changeset(%User{}, query_params)
with {:ok, user} <- Repo.insert(changeset),
{:ok, token, _claims} <- Guardian.encode_and_sign(user) do
conn
|> Conn.put_status(201)
|> render("jwt.json", jwt: token)
else
{:error, changeset} ->
conn
|> put_status(400)
|> render(ErrorView, "400.json", %{changeset: changeset})
end
end
Standard Swagger 2.0 JSON Reference:
How can I represent 'Authorization: Bearer <token>' in a Swagger Spec (swagger.json)
Okay, I think I got it! Adding security [%{Bearer: []}] to swagger_path passes the authorization token to the call.
Controller:
...
swagger_path :create_user do
post "/api/v1/users/create"
description "Create a user."
parameters do
user :body, Schema.ref(:Create), "User to save", required: true
end
security [%{Bearer: []}]
response 200, "Success"
end
def create_user(conn, query_params) do
changeset = User.changeset(%User{}, query_params)
with {:ok, user} <- Repo.insert(changeset),
{:ok, token, _claims} <- Guardian.encode_and_sign(user) do
conn
|> Conn.put_status(201)
|> render("jwt.json", jwt: token)
else
{:error, changeset} ->
conn
|> put_status(400)
|> render(ErrorView, "400.json", %{changeset: changeset})
end
end
...
Router:
...
def swagger_info do
%{
info: %{
version: "0.0.1",
title: "Server"
},
securityDefinitions: %{
Bearer: %{
type: "apiKey",
name: "Authorization",
in: "header"
}
}
}
end
...
This is something I need to look into myself. Here are a couple links that may help.
https://github.com/xerions/phoenix_swagger/blob/master/docs/getting-started.md#router
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object

Sending auth cookie using WS and ScalaTest

I have a web app which uses a form to login, this returns a session cookie to the user which is used to authorize requests to the rest of the app. I'm having trouble sending this cookie value with my requests. My test harness is below:
val loginResponse = await(WS.url(s"http://localhost:$port/authenticate")
.withHeaders("Content-Type" -> "application/x-www-form-urlencoded")
.post(Map("email" -> Seq("admin#example.com"), "password" -> Seq("genivirocks!"))))
loginResponse.status mustBe (OK)
val cookies = loginResponse.cookies(0).toString
val vehiclesResponse = await(WS.url(s"http://localhost:$port/api/v1/vehicles/" + testVin)
.withHeaders("Cookie" -> cookies)
.put(""))
vehiclesResponse.status mustBe (OK)
val vehiclesFilterResponse = await(WS.url(s"http://localhost:$port/api/v1/vehicles?regex=" + testVin)
.withHeaders("Cookie" -> cookies)
.get())
vehiclesFilterResponse.status mustBe (OK)
The request fails, as the second request gets a 204 instead of a 200, as it gets redirected to the login page because the cookie is interpreted as invalid. The web server gives the following error, when the second request is made:
2015-10-06 14:56:15,991
[sota-core-service-akka.actor.default-dispatcher-42] WARN
akka.actor.ActorSystemImpl - Illegal request header: Illegal 'cookie'
header: Invalid input 'EOI', expected tchar, '\r', WSP or '=' (line 1,
column 178):
PLAY2AUTH_SESS_ID=ee84a2d5a0a422a3e5446f82f9f3c6f8eda9db1cr~jz7ei0asg0hk.ebd8j.h4cpjj~~9c0(yxt8p*jqvgf)_t1.5b(7i~tly21(*id;
path=/; expires=1444139775000; maxAge=3600s; HTTPOnly
I've tried building the cookie string myself and making sure there are no extra '\r' characters at the end and so on, with no luck. Google also doesn't seem to have any hints. Is there a better way of sending cookie values using WS?
EDIT
Got it working with the following code:
import play.api.mvc.Cookies
val loginResponse = ...
loginResponse.status mustBe (OK)
val cookies = loginResponse.cookies
val cookie = Cookies.decodeCookieHeader(loginResponse.cookies(0).toString)
val vehiclesResponse = await(WS.url(s"http://localhost:$port/api/v1/vehicles/" + testVin)
.withHeaders("Cookie" -> Cookies.encodeCookieHeader(cookie))
.put(""))
vehiclesResponse.status mustBe (OK)
...
Why don't you use the existing Cookies.encode function to do the cookie encoding for you?
import play.api.mvc.Cookies
val loginResponse = ...
loginResponse.status mustBe (OK)
val cookies = loginResponse.cookies
val vehiclesResponse = await(WS.url(s"http://localhost:$port/api/v1/vehicles/" + testVin)
.withHeaders("Cookie" -> Cookies.encode(cookies))
.put(""))
vehiclesResponse.status mustBe (OK)
...