OBP-API/obp-api/src/test/scala/code/api/DirectLoginTest.scala

536 lines
24 KiB
Scala

package code.api
import code.api.Constant.localIdentityProvider
import code.api.util.ErrorMessages
import code.api.util.ErrorMessages._
import code.api.v2_0_0.OBPAPI2_0_0.Implementations2_0_0
import code.api.v3_0_0.OBPAPI3_0_0.Implementations3_0_0
import code.api.v3_0_0.UserJsonV300
import code.consumer.Consumers
import code.loginattempts.LoginAttempt
import code.model.dataAccess.AuthUser
import code.setup.{APIResponse, ServerSetup}
import code.userlocks.UserLocksProvider
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.json.JsonAST.{JArray, JField, JObject, JString}
import net.liftweb.mapper.By
import net.liftweb.util.Helpers._
import org.scalatest.{BeforeAndAfter, Tag}
class DirectLoginTest extends ServerSetup with BeforeAndAfter {
/**
* Test tags
* Example: To run tests with tag "getPermissions":
* mvn test -D tagsToInclude
*
* This is made possible by the scalatest maven plugin
*/
object VersionOfApi200 extends Tag(ApiVersion.v2_0_0.toString)
object VersionOfApi300 extends Tag(ApiVersion.v3_0_0.toString)
object ApiEndpoint1 extends Tag(nameOf(Implementations2_0_0.getCurrentUser))
object ApiEndpoint2 extends Tag(nameOf(Implementations3_0_0.getCurrentUser))
val KEY = randomString(40).toLowerCase
val SECRET = randomString(40).toLowerCase
val EMAIL = randomString(10).toLowerCase + "@example.com"
val USERNAME = "username with spaces"
//these are password, but sonarcloud: "password" detected here, make sure this is not a hard-coded credential.
val NO_EXISTING_PW = "notExistingPassword"
val VALID_PW = """G!y"k9GHD$D"""
val KEY_DISABLED = randomString(40).toLowerCase
val SECRET_DISABLED = randomString(40).toLowerCase
val EMAIL_DISABLED = randomString(10).toLowerCase + "@example.com"
val USERNAME_DISABLED = randomString(10).toLowerCase
val PASSWORD_DISABLED = randomString(20)
before {
if (AuthUser.find(By(AuthUser.username, USERNAME)).isEmpty)
AuthUser.create.
email(EMAIL).
username(USERNAME).
password(VALID_PW).
validated(true).
firstName(randomString(10)).
lastName(randomString(10)).
saveMe
if (Consumers.consumers.vend.getConsumerByConsumerKey(KEY).isEmpty)
Consumers.consumers.vend.createConsumer(Some(KEY), Some(SECRET), Some(true), Some("test application"), None, Some("description"), Some("eveline@example.com"), None, None).openOrThrowException(attemptedToOpenAnEmptyBox)
if (AuthUser.find(By(AuthUser.username, USERNAME_DISABLED)).isEmpty)
AuthUser.create.
email(EMAIL_DISABLED).
username(USERNAME_DISABLED).
password(PASSWORD_DISABLED).
validated(true).
firstName(randomString(10)).
lastName(randomString(10)).
saveMe
if (Consumers.consumers.vend.getConsumerByConsumerKey(KEY_DISABLED).isEmpty)
Consumers.consumers.vend.createConsumer(Some(KEY_DISABLED), Some(SECRET_DISABLED), Some(false), Some("test application disabled"), None, Some("description"), Some("eveline@example.com"), None, None).openOrThrowException(attemptedToOpenAnEmptyBox)
}
val accessControlOriginHeader = ("Access-Control-Allow-Origin", "*")
val invalidUsernamePasswordHeader = ("Authorization", ("DirectLogin username=\"notExistingUser\", " +
"password=%s, consumer_key=%s").format(NO_EXISTING_PW, KEY))
val invalidUsernamePasswordCharaterHeader = ("Authorization", ("DirectLogin username=\" a#s \", " +
"password=%s, consumer_key=%s").format(NO_EXISTING_PW, KEY))
val validUsernameInvalidPasswordHeader = ("Authorization", ("DirectLogin username=%s," +
"password=%s, consumer_key=%s").format(USERNAME, NO_EXISTING_PW, KEY))
val invalidConsumerKeyHeader = ("Authorization", ("DirectLogin username=%s, " +
"password=%s, consumer_key=%s").format(USERNAME, VALID_PW, "invalid"))
val validDeprecatedHeader = ("Authorization", "DirectLogin username=%s, password=%s, consumer_key=%s".
format(USERNAME, VALID_PW, KEY))
val validHeader = ("DirectLogin", "username=%s, password=%s, consumer_key=%s".
format(USERNAME, VALID_PW, KEY))
val disabledConsumerValidHeader = ("Authorization", "DirectLogin username=%s, password=%s, consumer_key=%s".
format(USERNAME_DISABLED, PASSWORD_DISABLED, KEY_DISABLED))
val invalidUsernamePasswordCharaterHeaders = List(accessControlOriginHeader, invalidUsernamePasswordCharaterHeader)
val invalidUsernamePasswordHeaders = List(accessControlOriginHeader, invalidUsernamePasswordHeader)
val validUsernameInvalidPasswordHeaders = List(accessControlOriginHeader, validUsernameInvalidPasswordHeader)
val invalidConsumerKeyHeaders = List(accessControlOriginHeader, invalidConsumerKeyHeader)
val validHeaders = List(accessControlOriginHeader, validHeader, ("Authorization", "Basic 123456"))
val validDeprecatedHeaders = List(accessControlOriginHeader, validDeprecatedHeader)
val disabledConsumerKeyHeaders = List(accessControlOriginHeader, disabledConsumerValidHeader)
def directLoginRequest = baseRequest / "my" / "logins" / "direct"
feature("DirectLogin") {
scenario("Invalid auth header") {
//setupUserAndConsumer
Given("the app we are testing is registered and active")
Then("We should be able to find it")
//val consumers = OBPConsumer.findAll()
//assert(registeredApplication(KEY) == true)
When("we try to login without an Authorization header")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", List(accessControlOriginHeader))
Then("We should get a 400 - Bad Request")
response.code should equal(400)
assertResponse(response, ErrorMessages.MissingDirectLoginHeader)
}
scenario("Invalid credentials") {
//setupUserAndConsumer
Given("the app we are testing is registered and active")
Then("We should be able to find it")
//assert(registeredApplication(KEY) == true)
When("we try to login with an invalid username/password")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", invalidUsernamePasswordHeaders)
Then("We should get a 401 - Unauthorized")
response.code should equal(401)
assertResponse(response, ErrorMessages.InvalidLoginCredentials)
}
scenario("Invalid Characters") {
When("we try to login with an invalid username Characters and invalid password Characters")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", invalidUsernamePasswordCharaterHeaders)
Then("We should get a 400 - Invalid Characters")
response.code should equal(400)
assertResponse(response, ErrorMessages.InvalidValueCharacters)
}
scenario("valid Username, invalid password, login in too many times. The username will be locked") {
When("login with an valid username and invalid password, failed more than 5 times.")
val request = directLoginRequest
var response = makePostRequestAdditionalHeader(request, "", validUsernameInvalidPasswordHeaders)
response = makePostRequestAdditionalHeader(request, "", validUsernameInvalidPasswordHeaders)
response = makePostRequestAdditionalHeader(request, "", validUsernameInvalidPasswordHeaders)
response = makePostRequestAdditionalHeader(request, "", validUsernameInvalidPasswordHeaders)
response = makePostRequestAdditionalHeader(request, "", validUsernameInvalidPasswordHeaders)
response = makePostRequestAdditionalHeader(request, "", validUsernameInvalidPasswordHeaders)
response = makePostRequestAdditionalHeader(request, "", validUsernameInvalidPasswordHeaders)
Then("We should get a 401 - the username has been locked")
response.code should equal(401)
assertResponse(response, ErrorMessages.UsernameHasBeenLocked)
Then("We login in with the valid username and valid passpord, the username still be locked ")
response = makePostRequestAdditionalHeader(request, "", validHeaders)
Then("We should get a 401 - the username has been locked")
response.code should equal(401)
assertResponse(response, ErrorMessages.UsernameHasBeenLocked)
Then("We unlock the username")
LoginAttempt.resetBadLoginAttempts(localIdentityProvider, USERNAME)
}
scenario("Consumer API key is disabled") {
Given("The app we are testing is registered and disabled")
When("We try to login with username/password")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", disabledConsumerKeyHeaders)
Then("We should get a 401")
response.code should equal(401)
assertResponse(response, ErrorMessages.InvalidConsumerKey)
}
scenario("Missing DirectLogin header") {
//setupUserAndConsumer
Given("the app we are testing is registered and active")
Then("We should be able to find it")
//assert(registeredApplication(KEY) == true)
When("we try to login with a missing DirectLogin header")
val request = directLoginRequest
val response = makePostRequest(request,"")
Then("We should get a 400 - Bad Request")
response.code should equal(400)
assertResponse(response, ErrorMessages.MissingDirectLoginHeader)
}
scenario("Login without consumer key") {
//setupUserAndConsumer
Given("the app we are testing is registered and active")
Then("We should be able to find it")
//assert(registeredApplication(KEY) == true)
When("the consumer key is invalid")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", invalidConsumerKeyHeaders)
Then("We should get a 401 - Unauthorized")
response.code should equal(401)
assertResponse(response, ErrorMessages.InvalidConsumerKey)
}
scenario("Login with correct everything! - Deprecated Header", ApiEndpoint1, ApiEndpoint2) {
//setupUserAndConsumer
Given("the app we are testing is registered and active")
Then("We should be able to find it")
//assert(registeredApplication(KEY) == true)
When("the header and credentials are good")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", validDeprecatedHeaders)
var token = "INVALID"
Then("We should get a 201 - OK and a token")
response.code should equal(201)
response.body match {
case JObject(List(JField(name, JString(value)))) =>
name should equal("token")
value.length should be > 0
token = value
case _ => fail("Expected a token")
}
// TODO Check that we are logged in. TODO Add an endpoint like /me that returns the currently logged in user.
When("when we use the token it should work")
val headerWithToken = ("Authorization", "DirectLogin token=%s".format(token))
val validHeadersWithToken = List(accessControlOriginHeader, headerWithToken)
val request2 = baseRequest / "obp" / "v2.0.0" / "my" / "accounts"
val response2 = makeGetRequest(request2, validHeadersWithToken)
Then("We should get a 200 - OK and an empty list of accounts")
response2.code should equal(200)
response2.body match {
case JArray(List()) =>
case _ => fail("Expected empty list of accounts")
}
When("when we use the token to get current user and it should work - New Style")
val requestCurrentUserNewStyle = baseRequest / "obp" / "v3.0.0" / "users" / "current"
val responseCurrentUserNewStyle = makeGetRequest(requestCurrentUserNewStyle, validHeadersWithToken)
And("We should get a 200")
responseCurrentUserNewStyle.code should equal(200)
val currentUserNewStyle = responseCurrentUserNewStyle.body.extract[UserJsonV300]
currentUserNewStyle.username shouldBe USERNAME
When("when we use the token to get current user and it should work - Old Style")
val requestCurrentUserOldStyle = baseRequest / "obp" / "v2.0.0" / "users" / "current"
val responseCurrentUserOldStyle = makeGetRequest(requestCurrentUserOldStyle, validHeadersWithToken)
And("We should get a 200")
responseCurrentUserOldStyle.code should equal(200)
val currentUserOldStyle = responseCurrentUserOldStyle.body.extract[UserJsonV300]
currentUserOldStyle.username shouldBe USERNAME
currentUserNewStyle.username shouldBe currentUserOldStyle.username
}
scenario("Login with correct everything!", ApiEndpoint1, ApiEndpoint2) {
//setupUserAndConsumer
Given("the app we are testing is registered and active")
Then("We should be able to find it")
//assert(registeredApplication(KEY) == true)
When("the header and credentials are good")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", validHeaders)
var token = "INVALID"
Then("We should get a 201 - OK and a token")
response.code should equal(201)
response.body match {
case JObject(List(JField(name, JString(value)))) =>
name should equal("token")
value.length should be > 0
token = value
case _ => fail("Expected a token")
}
// TODO Check that we are logged in. TODO Add an endpoint like /me that returns the currently logged in user.
When("when we use the token it should work")
val headerWithToken = ("Authorization", "DirectLogin token=%s".format(token))
val validHeadersWithToken = List(accessControlOriginHeader, headerWithToken)
val request2 = baseRequest / "obp" / "v2.0.0" / "my" / "accounts"
val response2 = makeGetRequest(request2, validHeadersWithToken)
Then("We should get a 200 - OK and an empty list of accounts")
response2.code should equal(200)
response2.body match {
case JArray(List()) =>
case _ => fail("Expected empty list of accounts")
}
When("when we use the token to get current user and it should work - New Style")
val requestCurrentUserNewStyle = baseRequest / "obp" / "v3.0.0" / "users" / "current"
val responseCurrentUserNewStyle = makeGetRequest(requestCurrentUserNewStyle, validHeadersWithToken)
And("We should get a 200")
responseCurrentUserNewStyle.code should equal(200)
val currentUserNewStyle = responseCurrentUserNewStyle.body.extract[UserJsonV300]
currentUserNewStyle.username shouldBe USERNAME
When("when we use the token to get current user and it should work - Old Style")
val requestCurrentUserOldStyle = baseRequest / "obp" / "v2.0.0" / "users" / "current"
val responseCurrentUserOldStyle = makeGetRequest(requestCurrentUserOldStyle, validHeadersWithToken)
And("We should get a 200")
responseCurrentUserOldStyle.code should equal(200)
val currentUserOldStyle = responseCurrentUserOldStyle.body.extract[UserJsonV300]
currentUserOldStyle.username shouldBe USERNAME
currentUserNewStyle.username shouldBe currentUserOldStyle.username
}
scenario("Login with correct everything and use props local_identity_provider", ApiEndpoint1, ApiEndpoint2) {
setPropsValues("local_identity_provider"-> Constant.HostName)
Given("the app we are testing is registered and active")
Then("We should be able to find it")
//assert(registeredApplication(KEY) == true)
When("the header and credentials are good")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", validHeaders)
var token = "INVALID"
Then("We should get a 201 - OK and a token")
response.code should equal(201)
response.body match {
case JObject(List(JField(name, JString(value)))) =>
name should equal("token")
value.length should be > 0
token = value
case _ => fail("Expected a token")
}
// TODO Check that we are logged in. TODO Add an endpoint like /me that returns the currently logged in user.
When("when we use the token it should work")
val headerWithToken = ("Authorization", "DirectLogin token=%s".format(token))
val validHeadersWithToken = List(accessControlOriginHeader, headerWithToken)
val request2 = baseRequest / "obp" / "v2.0.0" / "my" / "accounts"
val response2 = makeGetRequest(request2, validHeadersWithToken)
Then("We should get a 200 - OK and an empty list of accounts")
response2.code should equal(200)
response2.body match {
case JArray(List()) =>
case _ => fail("Expected empty list of accounts")
}
When("when we use the token to get current user and it should work - New Style")
val requestCurrentUserNewStyle = baseRequest / "obp" / "v3.0.0" / "users" / "current"
val responseCurrentUserNewStyle = makeGetRequest(requestCurrentUserNewStyle, validHeadersWithToken)
And("We should get a 200")
responseCurrentUserNewStyle.code should equal(200)
val currentUserNewStyle = responseCurrentUserNewStyle.body.extract[UserJsonV300]
currentUserNewStyle.username shouldBe USERNAME
When("when we use the token to get current user and it should work - Old Style")
val requestCurrentUserOldStyle = baseRequest / "obp" / "v2.0.0" / "users" / "current"
val responseCurrentUserOldStyle = makeGetRequest(requestCurrentUserOldStyle, validHeadersWithToken)
And("We should get a 200")
responseCurrentUserOldStyle.code should equal(200)
val currentUserOldStyle = responseCurrentUserOldStyle.body.extract[UserJsonV300]
currentUserOldStyle.username shouldBe USERNAME
currentUserNewStyle.username shouldBe currentUserOldStyle.username
}
scenario("Login with correct everything but the user is locked", ApiEndpoint1, ApiEndpoint2) {
lazy val username = "firstname.lastname"
lazy val header = ("DirectLogin", "username=%s, password=%s, consumer_key=%s".
format(username, VALID_PW, KEY))
// Delete the user
AuthUser.findAll(By(AuthUser.username, username)).map(_.delete_!())
// Create the user
AuthUser.create.
email(EMAIL).
username(username).
password(VALID_PW).
validated(true).
firstName(randomString(10)).
lastName(randomString(10)).
saveMe
When("the header and credentials are good")
lazy val response = makePostRequestAdditionalHeader(directLoginRequest, "", List(accessControlOriginHeader, header))
var token = ""
Then("We should get a 201 - OK and a token")
response.code should equal(201)
response.body match {
case JObject(List(JField(name, JString(value)))) =>
name should equal("token")
value.length should be > 0
token = value
case _ => fail("Expected a token")
}
When("when we use the token it should work")
lazy val headerWithToken = ("DirectLogin", "token=%s".format(token))
lazy val validHeadersWithToken = List(accessControlOriginHeader, headerWithToken)
// Lock the user in order to test functionality
UserLocksProvider.lockUser(localIdentityProvider, username)
When("when we use the token to get current user and it should NOT work due to locked user - New Style")
lazy val requestCurrentUserNewStyle = baseRequest / "obp" / "v3.0.0" / "users" / "current"
lazy val responseCurrentUserNewStyle = makeGetRequest(requestCurrentUserNewStyle, validHeadersWithToken)
And("We should get a 401")
responseCurrentUserNewStyle.code should equal(401)
responseCurrentUserNewStyle.body.extract[ErrorMessage].message should include(ErrorMessages.UsernameHasBeenLocked)
When("when we use the token to get current user and it should NOT work due to locked user - Old Style")
lazy val requestCurrentUserOldStyle = baseRequest / "obp" / "v2.0.0" / "users" / "current"
lazy val responseCurrentUserOldStyle = makeGetRequest(requestCurrentUserOldStyle, validHeadersWithToken)
And("We should get a 400")
responseCurrentUserOldStyle.code should equal(400)
responseCurrentUserOldStyle.body.extract[ErrorMessage].message should include(ErrorMessages.UsernameHasBeenLocked)
}
scenario("Test the last issued token is valid as well as a previous one", ApiEndpoint2) {
When("The header and credentials are good")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", validHeaders)
var token = ""
Then("We should get a 201 - OK and a token")
response.code should equal(201)
response.body match {
case JObject(List(JField(name, JString(value)))) =>
name should equal("token")
value.length should be > 0
token = value
case _ => fail("Expected a token")
}
val headerWithToken = ("DirectLogin", "token=%s".format(token))
val validHeadersWithToken = List(accessControlOriginHeader, headerWithToken)
When("When we use the token to get current user and it should work - New Style")
val requestCurrentUserNewStyle = baseRequest / "obp" / "v3.0.0" / "users" / "current"
val responseCurrentUserNewStyle = makeGetRequest(requestCurrentUserNewStyle, validHeadersWithToken)
And("We should get a 200")
responseCurrentUserNewStyle.code should equal(200)
val currentUserNewStyle = responseCurrentUserNewStyle.body.extract[UserJsonV300]
currentUserNewStyle.username shouldBe USERNAME
When("When we issue a new token")
makePostRequestAdditionalHeader(request, "", validHeaders)
Then("The previous one should be valid")
val secondResponse = makeGetRequest(requestCurrentUserNewStyle, validHeadersWithToken)
And("We should get a 200")
secondResponse.code should equal(200)
// assertResponse(failedResponse, DirectLoginInvalidToken)
}
scenario("Test DirectLogin header value is case insensitive", ApiEndpoint2) {
When("The header and credentials are good")
val request = directLoginRequest
val response = makePostRequestAdditionalHeader(request, "", validHeaders)
var token = ""
Then("We should get a 201 - OK and a token")
response.code should equal(201)
response.body match {
case JObject(List(JField(name, JString(value)))) =>
name should equal("token")
value.length should be > 0
token = value
case _ => fail("Expected a token")
}
val headerWithToken = ("dIreCtLoGin", "token=%s".format(token))
val validHeadersWithToken = List(accessControlOriginHeader, headerWithToken)
When("When we use the token to get current user and it should work - New Style")
val requestCurrentUserNewStyle = baseRequest / "obp" / "v3.0.0" / "users" / "current"
val responseCurrentUserNewStyle = makeGetRequest(requestCurrentUserNewStyle, validHeadersWithToken)
And("We should get a 200")
responseCurrentUserNewStyle.code should equal(200)
val currentUserNewStyle = responseCurrentUserNewStyle.body.extract[UserJsonV300]
currentUserNewStyle.username shouldBe USERNAME
When("When we issue a new token")
makePostRequestAdditionalHeader(request, "", validHeaders)
Then("The previous one should be valid")
val secondResponse = makeGetRequest(requestCurrentUserNewStyle, validHeadersWithToken)
And("We should get a 200")
secondResponse.code should equal(200)
// assertResponse(failedResponse, DirectLoginInvalidToken)
}
}
private def assertResponse(response: APIResponse, expectedErrorMessage: String): Unit = {
response.body.extract[ErrorMessage].message should startWith(expectedErrorMessage)
}
}