How to write APNs server side code in scala? - scala

I'm stuck at a very annoying problem.
I'm working with javaPNS and following one of the many guides on internet.
Here: https://code.google.com/p/javapns/wiki/PushNotificationAdvanced
/* Push your custom payload */
List<PushedNotification> notifications = Push.payload(payload, keystore, password, production, devices);
where you see this, above. It says that Push.payload() returns a List with PushedNotificaion.
Well, it doesn't in my code.
object Push {
def devPush(pushAlertMessage: String, badgeNumber: Int, devices: Seq[String]): List[PushedNotification] = {
//Retrieve the .p12 certification file
val keystoreFile = getClass.getResourceAsStream("keystorefile.p12")
//Create payload
val payload = PushNotificationPayload.complex()
payload.addBadge(badgeNumber)
payload.addAlert(pushAlertMessage)
payload.addSound("default")
//
val notifications:List[PushedNotification] = javapns.Push.payload(payload, keystoreFile, keystorePassword, false, devices)
for(notification <- javapns.Push.alert(pushAlertMessage, keystoreFile, keystorePassword, false, devices).getFailedNotifications){
/* Add code here to remove invalid tokens from database */
}
notifications
}
}
When I try to put a list in my val notifications with Push.payload it says:
"Express of type PushedNotifications doesn't conform to expected type List[PushedNotification]"
I'm tired and confused, not certain about the rest of the code either. Would appreciate any help and please. Correct my code if I'm wrong.

You are missing an implicit conversion from the java.util.List returned and the scala List that you want. Try adding the following import:
import scala.collection.JavaConversions._
And tweak this line:
val notifications:List[PushedNotification] = javapns.Push.payload(payload, keystoreFile, keystorePassword, false, devices)
To this:
val notifications:List[PushedNotification] = javapns.Push.payload(payload, keystoreFile, keystorePassword, false, devices).toList
Also, it looks like you will be pushing two times to each device here as the calls to payload and alert both push notifications to the devices. If you only really wanted to send the complex payload that you build, then your code should probably be:
val results = javapns.Push.payload(payload, keystoreFile, keystorePassword, false, devices)
for(notification <- result.getFailedNotifications.toList){
/* Add code here to remove invalid tokens from database */
}

Related

Karate-Gatling: Not able to use object fields inside Karate features

For the following Gatling simulation
class DeviceSimulation extends Simulation {
var devices: List[Device] = List[Device]()
before {
// Preparing data.
devices = DataFetch.getDevices()
}
// Feed device
val devicesFeederCont: Iterator[Map[String, Device]] = Iterator.continually(devices.map(d => {
Map("device" -> d)
})).flatten
val devicesFeederToKarate: ScenarioBuilder = scenario("feederDeviceToKarate").exec(karateSet("device", session => session("device").as[Device]))
val Devices: ScenarioBuilder = scenario("Device")
.feed(devicesFeederCont)
.exec(devicesFeederToKarate)
.exec(karateFeature("classpath:features/device/Devices.feature"))
setUp(
Devices.inject(rampUsers(5).during(5 seconds))
).protocols()
}
I would like to be able to inject Device object inside my feature:
Feature: Device actions
Background:
* url 'https://server-host'
* print 'Device obj: ', device
Scenario: Device actions
Given path '/api/device/name/', device.name
When method GET
Then status 200
But, although for the Background print I get: c.intuit.karate - [print] Device obj: Device(1234,989898989), for the GET request I have:
GET /api/device/name/com.intuit.karate.graal.JsExecutable#333d7..
I mention that Device is just a case class with two fields:
case class Device(id: Int, name: String).
Is there a way to properly use objects passed from feeder inside Karate features?
Right now we've tested only with primitive values passed into the Gatling session. It may work if you convert the data into a java.util.Map. So maybe your best bet is to write some toMap() function on your data-object. Or if you manage to emit a JSON string, there is a karate.fromString() helper that can be useful.
So please read the docs here and figure out what works: https://github.com/karatelabs/karate/tree/master/karate-gatling#gatling-session
You are most welcome to contribute code to improve the state of things.

Verify X-Hub-Signature from Facebook

I'm something of a beginner with the Play Framework (2.5 and Scala in this case) - and I'm trying to learn by building a bot for Facebook messenger. However I've gotten stuck trying to verify the signature of the messages.
I've followed the Facebook documentation and created a webhook. Which handles POST requests usinggetRawMessages (see code below). This then tries to verify that the request is signed by Facebook using the verifyPayload function. However I can't seem to get the computed and the actual hashes to match up.
I've taken a lead from looking at this question: How to verify Instagram real-time API x-hub-signature in Java? which seems to be doing pretty much what I want, but for the Instagram equivalent. But I still can't seem to get it right.
val secret = "<facebooks secret token>"
def getRawMessages = Action (parse.raw) {
request =>
val xHubSignatureOption = request.headers.get("X-Hub-Signature")
try {
for {
signature <- xHubSignatureOption
rawBodyAsBytes <- request.body.asBytes()
} yield {
val rawBody = rawBodyAsBytes.toArray[Byte]
val incomingHash = signature.split("=").last
val verified = verifyPayload(rawBody, secret, incomingHash)
Logger.info(s"Was verified? $verified")
}
Ok("Test")
}
catch {
case _ => Ok("Test")
}
}
val HMAC_SHA1_ALGORITHM = "HmacSHA1"
def verifyPayload(payloadBytes: Array[Byte], secret: String, expected: String): Boolean = {
val secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA1_ALGORITHM)
val mac = Mac.getInstance(HMAC_SHA1_ALGORITHM)
mac.init(secretKeySpec)
val result = mac.doFinal(payloadBytes)
val computedHash = Hex.encodeHex(result).mkString
Logger.info(s"Computed hash: $computedHash")
computedHash == expected
}
The Facebook webhook docs state:
The HTTP request will contain an X-Hub-Signature header which contains
the SHA1 signature of the request payload, using the app secret as the
key, and prefixed with sha1=. Your callback endpoint can verify this
signature to validate the integrity and origin of the payload
Please note that the calculation is made on the escaped unicode
version of the payload, with lower case hex digits. If you just
calculate against the decoded bytes, you will end up with a different
signature. For example, the string äöå should be escaped to
\u00e4\u00f6\u00e5.
I'm guessing that what I'm missing is getting the payload properly escaped to unicode, but I can't seem to find a way to do it. And the answer in the referenced question also appeared to just get the byte array without doing anything more with it (jsonRawBytes = jsonRaw.asBytes();).
Any help with how to proceed would be much appreciated.
Turns out I was using the wrong secret all along. Should anyone else make the same mistake, please note that it "App Secret" that is available on the application dashboard that you want. See the screenshot below.

PlayFramework Testing: Uploading File in Fake Request Errors

I want to test my method, which requires uploading a file. It is initialized like this:
val tempFile = TemporaryFile(new java.io.File("/home/ophelia/Desktop/students"))
val part = FilePart[TemporaryFile](
key = "students",
filename = "students",
contentType = Some("text/plain"),
ref = tempFile)
val files = Seq[FilePart[TemporaryFile]](part)
val formData = MultipartFormData(
dataParts = Map(),
files = Seq(part),
badParts = Seq(),
missingFileParts = Seq())
I pass it into the FakeRequest:
val result = route(
FakeRequest(POST, "/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
.withHeaders("Authorization" -> ("Session " + testSessionA.id.string))
.withMultipartFormDataBody(formData)
)
But when I run the test I get the following error:
Cannot write an instance of play.api.mvc.AnyContentAsMultipartFormData to HTTP response. Try to define a Writeable[play.api.mvc.AnyContentAsMultipartFormData]
What am I doing wrong and how to fix it? I looked on the internet, I didnt find any useful way to understand and resolve this problem.
It's important to remember that http requests are entirely text. route() takes an implicit Writeable to convert the body type of the provided request into text. Without the right Writeable, there is no way to know how to turn MultipartFormData into a request body.
There doesn't seem to be a Writeable for MultipartFormData, but you can provide your own. jroper has a great Writeable you could use for reference. (EDIT: That code is buggy, here's a working Writeable for AnyContentAsMultipartFormData)
Once you have your Writeable, you will need to make it accessible to your call to route(). Bear in mind, you currently have a FakeRequest[AnyContentAsMultipartFormData], not a FakeRequest[MultipartFormData]. You can either convert your request first:
val request = FakeRequest(POST,
"/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
.withHeaders("Authorization" -> ("Session "))
.withMultipartFormDataBody(formData)
route(request.map(_.mdf).asInstanceOf[FakeRequest[MultipartFormData[TemporaryFile]]])
or make your Writeable a Writeable[AnyContentAsMultipartFormData].
route for a given Request[T] requires an implicit parameter of type Writeable[T] that knows how to serialize the request body, because it will actually call the controller action just like an actual web request would, by pushing bytes onto it.
The problem is that there is no Writeable[MultipartFormData] predefined (you can see which are in play.api.test.Writeables).
This means you basically have two options:
write your own Writeable that serializes a MultipartFormData into bytes
Skip the routing part and call the action directly instead, like in the accepted answer in Play Framework Testing using MultipartFormData in a FakeRequest. This way of testing actions takes a shortcut and does not actually serialize and deserialize the request.
IMHO the first option is way too much pain for the gain, but if you go down that road, maybe contribute it to play when you succeed.
One of the possible solutions is to use wsUrl. For example
"File uploading action" should {
"upload sent file and result in ID" in {
val file = Paths.get(getClass.getResource("/1.txt").toURI)
val action = wsUrl("/upload").post(Source.single(FilePart("file", "hello.txt", Option("text/plain"), FileIO.fromPath(file))))
val res = Await.result(action, timeout)
res.status mustBe OK
res.body contains "123"
}
}

How to remove cookies on Finatra?

How do I remove a cookie after processing the request and building the response?
I have tried the following code, but it does not seem to work:
get("/login") { request =>
val message = request.cookies.get("flash-message").map(_.value)
request.removeCookie("flash-message")
render.view(LoginView(message)).toFuture
}
I could not find any methods on ResponseBuilder that would remove a cookie, either.
It turns out, the way to do it, is the usual "JavaScript" way. Just create an expired cookie and send back, like this:
import com.twitter.finagle.http.Cookie
import com.twitter.util.Duration
import java.util.concurrent.TimeUnit
get("/login") { request =>
val message = request.cookies.get("flash-message").map(_.value)
val c = Cookie("flash-message", "")
c.maxAge = Duration(-10, TimeUnit.DAYS)
render.view(LoginView(message)).cookie(c).toFuture
}
Of course 10 days is just an arbitrary "duration" in the past.

Play-mini: how to return an image

I'm trying to serve an image from a play-mini application.
object App extends Application {
def route = {
case GET(Path("/image")) => Action { request =>
Ok( Source.fromInputStream(getClass.getResourceAsStream("image.gif")).toArray ).as("image/gif")
}
}
}
Unfortunately, this does noe work :) I get the following error
Cannot write an instance of Array[Char] to HTTP response. Try to define a Writeable[Array[Char]]
Don't know about play-mini, but in play20 there is predefined Writeable[Array[Byte]], so you need to provide Array[Byte] for file handling. Also, there is a bit of documentation about serving files in play20.
I had the same problem and kept scratching my head for almost a week. Turned out the solution that worked for me was the following piece of code in my controller class:
def getPhoto(name: String) = Action {
val strPath = Paths.get(".").toAbsolutePath.toString() + "/public/photos/" + name
val file1: File = strPath
.toFile
val fileContent: Enumerator[Array[Byte]] = Enumerator.fromFile(new java.io.File(file1.path.toString))
Ok.stream(fileContent).as("image/jpeg")
}
And the route was defined as below:
GET /photos/:name controllers.myController.getPhoto(name)
Hence typing the URL with the photos extension displayed the photo on the browser like so: http://localhost:9000/photos/2018_11_26_131035.jpg
The image is saved in a folder "public/photos" in the root folder of the application and not necessarily the assets folder. Hope this helps someone :-)