Skip to content

Instantly share code, notes, and snippets.

@sadikovi
Created February 25, 2015 09:23
Show Gist options
  • Save sadikovi/55c3cad72304397f8fc7 to your computer and use it in GitHub Desktop.
Save sadikovi/55c3cad72304397f8fc7 to your computer and use it in GitHub Desktop.
Example of Gatling scenario that uses complex authentication with response processing (asking for auth-token, encrypting it, sending back, verifying logon). Each "browsing" request is sent, and based on response several sub-requests are generated, imitating drill-down into some piece of data on a website.
package systemsimulation.general
object AuthDataConst {
// constants
val DEFAULT_ITERATIONS = 1024;
val DEFAULT_BLOCK_SIZE = 256;
val DEFAULT_SALT = "Adfas245766&#N%^BW,.|%^&*";
// indicates whether the encryption is on or off
// affects key encryption and user token
val MOBILE_ENCRYPTION = true;
}
class AuthData(token: String, numberOfIterations: Int, blockSize: Int, MobileEncryption: Boolean, salt: String) {
val _token = token;
val _iterations = numberOfIterations;
val _blockSize = blockSize;
val _salt = salt;
val _encryption = MobileEncryption;
def this(token: String, iterations: Int, blocksize: Int, MobileEncryption: Boolean)
= this(token, iterations, blocksize, MobileEncryption, AuthDataConst.DEFAULT_SALT);
def this(token: String, iterations: Int, blocksize: Int)
= this(token, iterations, blocksize, AuthDataConst.MOBILE_ENCRYPTION, AuthDataConst.DEFAULT_SALT);
def this(token: String)
= this(token, AuthDataConst.DEFAULT_ITERATIONS, AuthDataConst.DEFAULT_BLOCK_SIZE, AuthDataConst.MOBILE_ENCRYPTION, AuthDataConst.DEFAULT_SALT);
def this(token: String, iterations: Int, encryption: Boolean)
= this(token, iterations, AuthDataConst.DEFAULT_BLOCK_SIZE, encryption, AuthDataConst.DEFAULT_SALT);
def getToken(): String = {
return _token;
}
def getIterations(): Int = {
return _iterations;
}
def getBlockSize(): Int = {
return _blockSize;
}
def getSalt(): String = {
return _salt;
}
def getEncryption(): Boolean = {
return _encryption;
}
}
package systemsimulation.general
import java.util.UUID
object DeviceDataConst {
val DEFAULT_TYPE:String = "Firefox";
val DEFAULT_VERSION:Int = 35;
val DEFAULT_INFO:String = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0";
def randomId() : String = {
return UUID.randomUUID().toString();
}
}
class DeviceData(cid: String, ctype: String, cversion: Int, caddInfo: String) {
// device id
val _id: String = cid;
// device type
val _type: String = ctype;
// device version
val _version: Int = cversion;
// device additional information
val _addInfo: String = caddInfo;
def this(cid: String, ctype: String, cversion: Int)
= this(cid, ctype, cversion, DeviceDataConst.DEFAULT_INFO);
def this(cid: String)
= this(cid, DeviceDataConst.DEFAULT_TYPE, DeviceDataConst.DEFAULT_VERSION);
def this()
= this(DeviceDataConst.randomId(), DeviceDataConst.DEFAULT_TYPE, DeviceDataConst.DEFAULT_VERSION);
def getId() : String = {
//return _id;
// generate random id every time method is called
return DeviceDataConst.randomId();
}
def getType() : String = {
return _type;
}
def getVersion() : Int = {
return _version;
}
def getAddInfo() : String = {
return _addInfo;
}
}
package systemsimulation.general
object DomainProxy {
val host = "localhost"
val port = 8080
}
package systemsimulation.general
class UserToken(username: Option[String], password: Option[String]) {
var _username = username match {
case None => "unknown"
case _ => username.get
};
var _password = password match {
case None => "unknown"
case _ => password.get
};
// data type for the user token
val _datatype = "BasicAuthenticationUserToken";
def changePassword(newPassword: String) {
_password = newPassword;
}
def getUsername() : String = {
return _username;
}
def getPassword() : String = {
return _password;
}
def getDatatype() : String = {
return _datatype;
}
def getJsonString() : String = {
var json = """{"DataType":"""" + _datatype + """",""" +
""""Username":"""" + _username + """",""" +
""""Password":"""" + _password + """"}""";
return json;
}
}
package systemsimulation.general
import scala.util.parsing.json._
import scala.util.Random
object Util {
def _createPair(key: String, value: Any) : String = value match {
case a:String => "\"" + key + "\"" + ":" + "\"" + a + "\"";
case b:Map[String,Any] => "\"" + key + "\"" + ":" + jsonFromMap(b);
case _ => "\"" + key + "\"" + ":" + "\"" + "_unknown_" + "\"";
}
def _wrapJsonString(json: String) : String = {
return "{ " + json + " }";
}
def jsonFromMap(map: Map[String,Any]) : String = {
var jsonList:List[String] = List();
map.keys.foreach {
key =>
var pair:String = _createPair(key, map(key));
jsonList = jsonList ::: List(pair)
}
var jsonBody:String = jsonList.mkString(",");
return _wrapJsonString(jsonBody);
}
def mapFromJsonString(jsonString: String) : Map[String, Any] = {
var option:Option[Any] = JSON.parseFull(jsonString);
var json = option.get.asInstanceOf[Map[String, Any]];
return json;
}
// generate random string based on ASCII table
def _randomStringFromASCII(length: Int, minCharCode: Int, maxCharCode: Int) : String = {
val (min, max) = (minCharCode, maxCharCode);
def nextDigit = Random.nextInt(max - min) + min
return new String(Array.fill(length)(nextDigit.toByte), "ASCII");
}
// generate random string, sort of password
def randomString(length: Int) : String = {
return _randomStringFromASCII(length, 33, 126);
}
// generate random string using only letters
def randomName(length: Int) : String = {
return _randomStringFromASCII(length, 97, 122);
}
// random index
def randomIndex(min:Int, max:Int): Int = {
if (min >= max)
return min;
return Random.nextInt(max - min) + min;
}
}
package systemsimulation
import io.gatling.core.Predef._
import io.gatling.core.session._
import io.gatling.http.Predef._
import scala.concurrent.duration._
import general._
class SystemSimulation extends Simulation {
// configure proxy
val httpProxy = Proxy(DomainProxy.host, DomainProxy.port)
// json file feeder
val JSON_FEEDER = "login.json"
// number of threads (users)
val SIMULATION_THREADS = 100
// number of times to try login process
var LOGIN_ATTEMPTS = 1
// number of seconds user spends on webpage
object WebpageViewTime {
val min = 5;
val max = 10;
}
// set headers
val commonHeaders = Map(
"Host" -> "unwired.mysuperworld.com",
"User-Agent" -> "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0",
"Accept" -> "application/json, text/javascript, */*; q=0.01",
"Accept-Language" -> "en-US,en;q=0.5",
"DNT" -> "1",
"X-Requested-With" -> "XMLHttpRequest",
"Referer" -> "https://unwired.mysuperworld.com/mrddres_residential/",
"Connection" -> "keep-alive"
)
// configure basic http
val httpConf = http
.proxy(httpProxy)
.baseURL("https://unwired.mysuperworld.com/mrddres_residential")
.headers(commonHeaders)
// object Actions to parse response and return new AuthData
object Actions {
// fetch token key and number of iterations and store them back in session
def retrieveAuthData(session: Session, datakey: String) : AuthData = {
// get response as a string
var t:String = session.get(datakey).as[String];
// remove everything from the beginning of the string to reach first symbol "{"
var altered:String = t.slice(t.indexOfSlice("{"), t.length);
var a:Map[String, Any] = Util.mapFromJsonString(altered);
// retrieve parameters from json
var token:String = a("Token").asInstanceOf[String];
var iterations:Int = a("KeyStrengtheningIterations").asInstanceOf[Double].toInt;
var mobileEncryption:Boolean = a("MobileEncryption").asInstanceOf[Boolean];
return new AuthData(token, iterations, mobileEncryption);
}
// extract json map from session for datakey
def getJsonMap(session: Session, datakey: String) : Map[String, Any] = {
var t:String = session.get(datakey).as[String]
var altered:String = t.slice(t.indexOfSlice("{"), t.length);
var a:Map[String, Any] = Util.mapFromJsonString(altered);
return a;
}
def extractLocations(session:Session, datakey:String) : List[String] = {
var _list:List[String] = List();
var a = Actions.getJsonMap(session, datakey);
var icps = a("Icps").asInstanceOf[List[Any]];
for (x <- icps) {
var cn:String = (x.asInstanceOf[Map[String, String]])("IcpNumber");
if (cn != null) { _list = _list :+ cn; }
}
return _list;
}
def extractCurrentPeriodDate(session:Session, datakey:String) : String = {
var a = Actions.getJsonMap(session, datakey);
var currentPeriodDate = a("CurrentPeriodDate").asInstanceOf[String];
return currentPeriodDate;
}
def extractDatesList(session:Session, datakey:String) : List[String] = {
var _list:List[String] = List();
var a = Actions.getJsonMap(session, datakey);
var usage = a("Usage").asInstanceOf[Map[String, String]];
var lDate:String = usage("LastDataReceivedDate");
var fDate:String = usage("FirstDataReceivedDate");
if (lDate != null) {_list = _list :+ lDate;}
if (fDate != null) { _list = _list :+ fDate;}
return _list;
}
// extract dates for drill down
def extractDrills(session:Session, datakey:String) : List[String] = {
var _list:List[String] = List();
var a = Actions.getJsonMap(session, datakey);
var b = a("GraphInUnits").asInstanceOf[Map[String, Any]];
var graph = b("Graph").asInstanceOf[Map[String, Any]];
var graphBars = graph("GraphBars").asInstanceOf[List[Map[String, Any]]];
// fill list with possible drills
for (key <- graphBars) {
var action = key("ViewActionPeriod").asInstanceOf[Map[String, Any]];
if (action != null && action.contains("DocumentToPost")) {
var documentToPost = Util.jsonFromMap(action("DocumentToPost").asInstanceOf[Map[String, String]]);
if (documentToPost != null) { _list = _list :+ documentToPost; }
}
}
return Actions._randomlyFilterList(_list);
}
// extract feature actions
def extractFeatureActions(session:Session, datakey:String) : List[String] = {
var _list:List[String] = List();
var a = Actions.getJsonMap(session, datakey);
var features = a("ChangeGraphFeaturesActions").asInstanceOf[List[Map[String, Any]]];
// fill list with possible drills
for (key <- features) {
var action = key("DocumentToPost").asInstanceOf[Map[String, Any]];
if (action != null) {
var documentToPost = Util.jsonFromMap(action.asInstanceOf[Map[String, String]]);
if (documentToPost != null) { _list = _list :+ documentToPost; }
}
}
return Actions._randomlyFilterList(_list);
}
// randmoly filter list that is passed and return a new list
def _randomlyFilterList(list: List[String]) : List[String] = {
var _selectedList:List[String] = List();
if (list.length > 0) {
// get upper bound as a number between 1 and 3
var n:Int = Util.randomIndex(1, 3);
// check if n is in list range
if (list.length < n) { n = list.length; }
var bound:Int = list.length-1;
for (i <- 0 to n) {
var j:Int = Util.randomIndex(0, bound);
_selectedList = _selectedList :+ list(j);
}
}
return _selectedList;
}
}
// create feeder with credentials
val feeder = jsonFile(JSON_FEEDER).queue;
// Auth actions
object Auth {
var device = new DeviceData();
// pick user from feeder
// and set username, password, and deviceId
var pickUser = feed(feeder)
// different actions for authentication
// 1. check if session is on, that we have logged in
val checkSessionIsOn = exec(
http("check_session_is_on")
.get("/webservices/mobile")
.check(status.is(200))
)
// 2. check if session is off, that we are not logged in
val checkSessionIsOff = exec(
http("check_session_is_off")
.get("/webservices/mobile")
.check(status.is(401))
)
// 3. login chain
// login chain of requests
val login = tryMax(LOGIN_ATTEMPTS) {
exec(
http("requesting_onetime_token")
.post("/webservices/mobile/core/oneTimeLoginToken")
.headers(Map(
"Accept" -> "application/json, text/javascript, */*; q=0.01",
"Content-Type" -> "application/x-www-form-urlencoded; charset=UTF-8"
)
)
.body(StringBody(
Util.jsonFromMap(Map(
"DocumentType" -> "OneTimeLoginTokenRequestDocument",
"Username" -> "${username}",
"DeviceId" -> "${deviceId}",
"DeviceType" -> device.getType()
))
))
.check(status.is(200))
.check(bodyString.saveAs("mobile_response"))
)
.exec{ session =>
var userToken = new UserToken(
session("username").asOption[String],
session("password").asOption[String]
);
//println(session("username").as[String] + "->" + session("password").as[String])
val authdata = Actions.retrieveAuthData(session, "mobile_response");
val encToken = MobileEncryption.encryptUserToken(userToken, authdata);
session.set("mobile_encryptedUserToken", encToken);
}
.exec(
http("sending_login_token")
.post("/webservices/mobile/core/sessions")
.body(StringBody(
Util.jsonFromMap(Map(
"DocumentType" -> "SessionCreationDocument",
"Username" -> "${username}",
"UserToken" -> "${mobile_encryptedUserToken}",
"DeviceInformation" -> Map(
"AdditionalInformation" -> device.getAddInfo(),
"DeviceModel" -> device.getVersion().toString,
"DeviceType" -> device.getType(),
"DeviceId" -> "${deviceId}"
)
))
))
.check(status.is(200))
)
.exec(
http("checking_login_success")
.get("/webservices/mobile")
.check(status.is(200))
)
}.exitHereIfFailed
// logout - server side logging out + clearing all the cookies
var logout = exec{ session =>
var usertoken = new UserToken(
session("username").asOption[String],
session("password").asOption[String]
);
val authdata = new AuthData("");
val encToken = MobileEncryption.encryptUserToken(usertoken, authdata);
session.set("mobile_logoutToken", encToken);
}
.exec(
http("logging_out")
.post("/webservices/mobile/core/sessions/current")
.body(StringBody("${mobile_logoutToken}"))
.check(status.is(200))
)
.exec(flushSessionCookies)
}
// additional super login flow
object SuperLoginFlow {
val sendCustomerNumber = exec(
http("customer_send_number")
.post("/webservices/arc/residential/users/all")
.headers(Map(
"Accept" -> "application/json, text/javascript, */*; q=0.01",
"Content-Type" -> "application/x-www-form-urlencoded; charset=UTF-8"
))
.body(StringBody(
Util.jsonFromMap(Map(
"CustomerId" -> "${customerNumber}",
"DocumentType" -> "ConsumerUsageSummaryRequest"
))
))
.check(status.is(200))
)
val getUserInfo = exec(
http("super_get_user_info")
.get("/webservices/arc/residential/users/byid/${customerNumber}")
.check(status.is(200))
)
/* call for start browsing */
// it will automatically call every method for a particular location/date
val startBrowsing = exec(
http("super_get_start_browsing")
.get("/webservices/arc/residential/powerusage/all")
.check(status.is(200))
.check(bodyString.saveAs("super_powerusage_all"))
)
.pause(WebpageViewTime.min, WebpageViewTime.max)
.exec { session =>
var loc = Actions.extractLocations(session, "super_powerusage_all")
var date = Actions.extractCurrentPeriodDate(session, "super_powerusage_all")
session
.set("super_locations", loc)
.set("super_current_date", date)
}
.foreach("${super_locations}", "location") {
ViewLocation.run
}
}
object ViewLocation {
var run = exec(
http("super_customer_location")
.post("/webservices/arc/residential/powerusage/all")
.check(status.is(200))
.body(StringBody(Util.jsonFromMap(Map(
"ConsumerName" -> "${location}",
"Mode" -> "Cumulative",
"RequestDate" -> "${super_current_date}",
"TimePeriod" -> "Month",
"FeatureMode" -> "ShowTotalUsage",
"DocumentType" -> "ConsumerDetailRequest"
))))
.check(bodyString.saveAs("super_powerusage_location"))
)
.pause(WebpageViewTime.min, WebpageViewTime.max)
.exec { session =>
val ses = "super_powerusage_location"
var dates = Actions.extractDatesList(session, ses)
session
.set("super_dates_list", dates)
.set("super_powerusage_location", "1")
}
.foreach("${super_dates_list}", "super_search_date") {
ViewYear.run
}
}
//
object ViewYear {
var run = exec(
http("super_customer_year_view")
.post("/webservices/arc/residential/powerusage/all")
.body(StringBody(Util.jsonFromMap(Map(
"ConsumerName" -> "${location}",
"Mode" -> "Cumulative",
"RequestDate" -> "${super_search_date}",
"TimePeriod" -> "Year",
"FeatureMode" -> "ShowTotalUsage",
"DocumentType" -> "ConsumerDetailRequest"
))))
.check(bodyString.saveAs("super_powerusage_location_year"))
)
.pause(WebpageViewTime.min, WebpageViewTime.max)
.exec { session =>
val ses = "super_powerusage_location_year"
var bars = Actions.extractDrills(session, ses)
val features = Actions.extractFeatureActions(session, ses)
session
.set("super_temp_post", bars)
.set("super_temp_new_features", features)
.set("super_powerusage_location_year", "1")
}
.foreach("${super_temp_post}", "super_document_to_post_month") {
ViewMonth.run
}
.foreach("${super_temp_new_features}", "super_document_to_post_year_new_feature") {
ChangeFeature.run
}
}
object ViewMonth {
val run = exec(
http("super_customer_month_view")
.post("/webservices/arc/residential/powerusage/all")
.check(status.is(200))
.body(StringBody("${super_document_to_post_month}"))
.check(bodyString.saveAs("super_powerusage_location_month"))
)
.pause(WebpageViewTime.min, WebpageViewTime.max)
.exec { session =>
val ses = "super_powerusage_location_month"
var bars = Actions.extractDrills(session, ses)
session
.set("super_temp_post_month", bars)
.set("super_powerusage_location_month", "1")
}
.foreach("${super_temp_post_month}", "super_document_to_post_day") {
ViewDay.run
}
}
object ViewDay {
val run = exec(
http("super_customer_day_view")
.post("/webservices/arc/residential/powerusage/all")
.check(status.is(200))
.body(StringBody("${super_document_to_post_day}"))
)
.pause(WebpageViewTime.min, WebpageViewTime.max)
}
object ChangeFeature {
val run = exec(
http("super_customer_year_new_feature")
.post("/webservices/arc/residential/powerusage/all")
.check(status.is(200))
.body(StringBody("${super_document_to_post_year_new_feature}"))
)
.pause(WebpageViewTime.min, WebpageViewTime.max)
}
// create scenario for one user
val superAdmin = scenario("Super Admin")
.exec(
Auth.pickUser,
Auth.checkSessionIsOff,
Auth.login,
Auth.checkSessionIsOn,
SuperLoginFlow.sendCustomerNumber,
SuperLoginFlow.getUserInfo,
SuperLoginFlow.startBrowsing,
Auth.logout,
Auth.checkSessionIsOff
)
// set it all up
setUp(
superAdmin.inject(atOnceUsers(SIMULATION_THREADS))
).protocols(httpConf)
}
@swapnil-kotwal-sp
Copy link

Thanks @sadikovi this really very helpful. I'm facing similar kind of problem, could you please check
Here is my scenario.

val header = Map(
    "Accept" -> """application/json""",
    "Content-Type" -> """application/x-www-form-urlencoded, charset=UTF-8""")

val auth_token = scenario("POST Authentication")
        .exec(
            http("POST OAuth Req")
            .post("/validate/activationURL")
            .formParam("oauth.token.client.secret", "*******")
            .formParam("oauth.token.url", "https://myweb.com/as/token.oauth2")
            .formParam("oauth.token.client.id", "my-test-user")
            .formParam("oauth.token.grant.type", "client_credentials")
            .formParam("oauth.token.scope", "my:test:activation")
            .headers(header)
    )

But I'm getting error as


=========================
HTTP request:
POST https://my-validation.net/validate/activationURL
headers=
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Content-Length: 305
Host: https://my-validation.net/
params=
oauth.token.client.secret: *******
oauth.token.url: https://myweb.com/as/token.oauth2
oauth.token.client.id: my-test-user
oauth.token.grant.type: client_credentials
oauth.token.scope: my:test:activation
=========================
HTTP response:
status=
401 Unauthorized
headers= 
Cache-Control: no-store
Content-Type: application/json;charset=UTF-8
Date: Fri, 03 Feb 2017 03:30:40 GMT
Pragma: no-cache
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
Www-Authenticate: Bearer realm="oauth2-resource", error="unauthorized", error_description="Full authentication is required to access this resource"
X-Cf-Requestid: 4681a3da-a747-40f7-4801-b6b972b07cf6
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Xss-Protection: 1; mode=block
Content-Length: 102

body=
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
<<<<<<<<<<<<<<<<<<<<<<<<<
20:30:40.972 [DEBUG] i.g.h.a.ResponseProcessor - 
>>>>>>>>>>>>>>>>>>>>>>>>>>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment