| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- import com.weEat.controllers.UserController
- import play.api.test.Helpers._
- import play.api.test.FakeRequest
- import play.api.mvc.{Results,Headers}
- import play.api.libs.json.Json
- import org.scalatest.BeforeAndAfterAll
- import org.scalatestplus.play._
- import org.scalatestplus.play.guice.GuiceOneServerPerSuite
- import com.weEat.shared.models.{User,UserAuthorization}
- import java.util.Base64
- import org.bson.types.ObjectId
- import scala.concurrent.Future
- import javax.inject.Inject
- class OAuthSpec @Inject()(
- userController: UserController
- ) extends PlaySpec
- with BeforeAndAfterAll
- with Results
- with GuiceOneServerPerSuite {
- val email = "tuser@sample.org"
- val password = "password"
- val fname = "test"
- val lname = "user"
- val users = Seq(
- (User(new ObjectId(), "test", "user", "tuser@sample.org"), "password"),
- (User(new ObjectId(), "another", "user", "usert@sample.org"), "password")
- )
- implicit class CSRFWrapper[T](requ: FakeRequest[T]) {
- def withCSRFToken() = requ.withHeaders(
- requ.headers.add(("Csrf-Token", "nocheck"))
- )
- }
- def makeAuthHeader(username: String, password: String) =
- Headers(("Authorization" -> s"Basic ${new String(Base64.getEncoder.encode(s"$username:$password".getBytes))}"))
- def makeAuthHeader(auth: UserAuthorization) =
- Headers(("Authorization" -> s"${auth.tokenType} ${auth.accessToken}"))
- override def beforeAll() = {
- for ((User(_, fname, lname, email), password) <- users) {
- val Some(resp) = route(app, FakeRequest(PUT, "/v1/user/").withJsonBody(
- Json.parse(s"""|{
- | "fname": "$fname",
- | "lname": "$lname",
- | "email": "$email",
- | "password": "$password"
- |}""".stripMargin)
- ).withCSRFToken())
- status(resp) mustEqual OK
- }
- }
- override def afterAll() = {
- implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global
- Future.sequence(Seq(
- userController.deleteUser(users(0)._1._id),
- )).map { (deletes) =>
- deletes must matchPattern {
- case Seq(_) =>
- }
- }
- // val Some(resp) = route(app, FakeRequest(DELETE, s"/v1/user/${users(0)._id}"))
- // status(resp) mustEqual OK
- // val Some(resp) = route(app, FakeRequest(DELETE, s"/v1/user/${users(1)._id}"))
- // status(resp) mustEqual OK
- }
- "the token endpoint" should {
- "reject an empty request" in {
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- )
- status(resp) mustEqual BAD_REQUEST
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_request\", "+
- "error_description=\"required parameter: grant_type\"")
- )
- }
- "reject an improperly formatted request" in {
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withBody("""{"grant_type" "password"}""")
- .withHeaders(
- makeAuthHeader(email, password).add(("Content-Type", "application/json"))
- ).withCSRFToken()
- )
- status(resp) mustEqual BAD_REQUEST
- // Won't get an OAuth response header. Will fail before processing.
- }
- "reject a missing grant_type" in {
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("{}"))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- )
- status(resp) mustEqual BAD_REQUEST
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_request\", "+
- "error_description=\"required parameter: grant_type\"")
- )
- }
- }
- "the token endpoint for password grants" should {
- "accept a valid login" in {
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- )
- status(resp) mustEqual OK
- contentAsJson(resp).as[UserAuthorization] must matchPattern {
- case UserAuthorization(_, _, _, _, _) =>
- }
- }
- "issue unique access tokens for successive logins" in {
- val requ = FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- val Some(resp1) = route(app, requ)
- val Some(resp2) = route(app, requ)
- status(resp2) mustEqual OK
- val resp1Auth = contentAsJson(resp1).as[UserAuthorization]
- val resp2Auth = contentAsJson(resp2).as[UserAuthorization]
- resp1Auth must matchPattern {
- case UserAuthorization(_, _, _, _, _) =>
- }
- resp2Auth must matchPattern {
- case UserAuthorization(_, _, _, _, _) =>
- }
- resp1Auth.accessToken mustNot equal(resp2Auth.accessToken)
- resp1Auth.refreshToken mustNot equal(resp2Auth.refreshToken)
- }
- "reject an unregistered user" in {
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(
- "_"+users(0)._1.email,
- "_"+users(0)._2
- ))
- .withCSRFToken()
- )
- status(resp) mustEqual UNAUTHORIZED
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_client\", "+
- "error_description=\"Invalid client or client is not authorized\""))
- }
- "reject an incorrect password" in {
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, "_"+users(0)._2))
- .withCSRFToken()
- )
- status(resp) mustEqual UNAUTHORIZED
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_client\", "+
- "error_description=\"Invalid client or client is not authorized\"")
- )
- }
- "reject an incorrect username" in {
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader("_"+users(0)._1.email, users(0)._2))
- .withCSRFToken()
- )
- status(resp) mustEqual UNAUTHORIZED
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_client\", "+
- "error_description=\"Invalid client or client is not authorized\"")
- )
- }
- "reject an requests without Authorization headers" in {
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse(s"""{"grant_type": "password"}""")
- ).withCSRFToken())
- status(resp) mustEqual BAD_REQUEST
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_request\", "+
- "error_description=\"Client credential is not found\"")
- )
- }
- }
- "the token endpoint for refresh grants" should {
- "accept a given refresh token" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withCSRFToken()
- )
- status(resp) mustEqual OK
- val newAuth = contentAsJson(resp).as[UserAuthorization]
- newAuth must matchPattern {
- case UserAuthorization(_, _, _, _, _) =>
- }
- newAuth.accessToken mustNot equal(auth.accessToken)
- newAuth.refreshToken mustNot equal(auth.refreshToken)
- }
- "reject a given refresh token on the second attempt" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val Some(resp1) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withCSRFToken()
- )
- status(resp1) mustEqual OK
- contentAsJson(resp1).as[UserAuthorization] must matchPattern {
- case UserAuthorization(_, _, _, _, _) =>
- }
- val Some(resp2) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withCSRFToken()
- )
- status(resp2) mustEqual UNAUTHORIZED
- headers(resp2) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_client\", "+
- "error_description=\"Invalid client or client is not authorized\"")
- )
- }
- "reject a revoked refresh token" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val Some(resp1) = route(app, FakeRequest(DELETE, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withHeaders(makeAuthHeader(auth))
- .withCSRFToken()
- )
- status(resp1) mustEqual OK
- val Some(resp2) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withCSRFToken()
- )
- status(resp2) mustEqual UNAUTHORIZED
- headers(resp2) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_client\", "+
- "error_description=\"Invalid client or client is not authorized\"")
- )
- }
- "reject a given refresh token with an incorrect username" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "_${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withCSRFToken()
- )
- status(resp) mustEqual UNAUTHORIZED
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_client\", "+
- "error_description=\"Invalid client or client is not authorized\"")
- )
- }
- "reject an unprovided refresh token" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val fakeToken = s"${auth.refreshToken.charAt(0)+1}${auth.refreshToken.substring(1)}";
- val Some(resp) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "$fakeToken"
- |}""".stripMargin))
- .withCSRFToken()
- )
- status(resp) mustEqual UNAUTHORIZED
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_client\", "+
- "error_description=\"Invalid client or client is not authorized\"")
- )
- }
- }
- "the secured endpoint" should {
- "accept an authorized request" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(email, password))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val Some(resp) = route(app, FakeRequest(GET, "/v1/user/self/name/")
- .withHeaders(makeAuthHeader(auth))
- .withCSRFToken()
- )
- status(resp) mustEqual OK
- contentAsString(resp) must
- equal(s"${users(0)._1.fname} ${users(0)._1.lname}")
- }
- "reject an unauthorized request" in {
- val Some(resp) = route(app, FakeRequest(GET, "/v1/user/self/name/")
- .withCSRFToken()
- )
- status(resp) mustEqual BAD_REQUEST
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_request\", "+
- "error_description=\"Access token is not found\"")
- )
- }
- "reject an forged authorized request" in {
- val fakeToken = "7pKNy790TV5lKVjQw3k/pwJmMS8XBhHaLTVaI6ftd5M="
- val Some(resp) = route(app, FakeRequest(GET, "/v1/user/self/name/")
- .withHeaders(("Authorization" -> s"Bearer $fakeToken"))
- .withCSRFToken()
- )
- status(resp) mustEqual UNAUTHORIZED
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_token\", "+
- "error_description=\"The access token is not found\"")
- )
- }
- "reject a refreshed access token" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val Some(resp1) = route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withCSRFToken()
- )
- status(resp1) mustEqual OK
- val Some(resp2) = route(app, FakeRequest(GET, "/v1/user/self/name/")
- .withHeaders(makeAuthHeader(auth))
- .withCSRFToken()
- )
- status(resp2) mustEqual UNAUTHORIZED
- headers(resp2) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_token\", "+
- "error_description=\"The access token is invalid\"")
- )
- }
- "reject a revoked access token" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val Some(resp1) = route(app, FakeRequest(DELETE, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withHeaders(makeAuthHeader(auth))
- .withCSRFToken()
- )
- status(resp1) mustEqual OK
- val Some(resp2) = route(app, FakeRequest(GET, "/v1/user/self/name/")
- .withHeaders(makeAuthHeader(auth))
- .withCSRFToken()
- )
- status(resp2) mustEqual UNAUTHORIZED
- headers(resp2) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_token\", "+
- "error_description=\"The access token is invalid\"")
- )
- }
- /*
- "reject an expired access token" taggedAs(Slow) in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(email, password))
- ).get).as[UserAuthorization]
- Thread.sleep((1 hour).toMillis)
- val Some(resp) = route(app, FakeRequest(GET, "/v1/user/self/name/")
- .withHeaders(makeAuthHeader(auth)))
- status(resp) mustEqual UNAUTHORIZED
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_token\", "+
- "error_description=\"The access token is invalid\"")
- )
- }
- */
- }
- "the token endpoint for revokation requests" should {
- "accept a valid request" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val Some(resp) = route(app, FakeRequest(DELETE, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withHeaders(makeAuthHeader(auth))
- .withCSRFToken()
- )
- status(resp) mustEqual OK
- contentAsString(resp) mustEqual("")
- }
- "reject a request with an invalid authorization header" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val fakeToken = s"${auth.accessToken.charAt(0)+1}${auth.accessToken.substring(1)}";
- val Some(resp) = route(app, FakeRequest(DELETE, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withHeaders("Authorization" -> s"Bearer $fakeToken")
- .withCSRFToken()
- )
- status(resp) mustEqual UNAUTHORIZED
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_token\", "+
- "error_description=\"The access token is not found\"")
- )
- }
- "reject a request without an authorization header" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val Some(resp) = route(app, FakeRequest(DELETE, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withCSRFToken()
- )
- status(resp) mustEqual BAD_REQUEST
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_request\", "+
- "error_description=\"Access token is not found\"")
- )
- }
- "reject a request with an unissued refresh token" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val fakeToken = s"${auth.refreshToken.charAt(0)+1}${auth.refreshToken.substring(1)}";
- val Some(resp) = route(app, FakeRequest(DELETE, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "${users(0)._1.email}",
- | "refresh_token": "${fakeToken}"
- |}""".stripMargin))
- .withHeaders(makeAuthHeader(auth))
- .withCSRFToken()
- )
- status(resp) mustEqual UNAUTHORIZED
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_token\", "+
- "error_description=\"The access token is invalid\"")
- )
- }
- "reject a request with an incorrect username" in {
- val auth = contentAsJson(route(app, FakeRequest(POST, "/authorize/")
- .withJsonBody(Json.parse("""{"grant_type": "password"}"""))
- .withHeaders(makeAuthHeader(users(0)._1.email, users(0)._2))
- .withCSRFToken()
- ).get).as[UserAuthorization]
- val Some(resp) = route(app, FakeRequest(DELETE, "/authorize/")
- .withJsonBody(Json.parse(s"""|{
- | "grant_type": "refresh_token",
- | "client_id": "_${users(0)._1.email}",
- | "refresh_token": "${auth.refreshToken}"
- |}""".stripMargin))
- .withHeaders(makeAuthHeader(auth))
- .withCSRFToken()
- )
- status(resp) mustEqual UNAUTHORIZED
- headers(resp) must contain ("WWW-Authenticate" ->
- ("Bearer error=\"invalid_client\", "+
- "error_description=\"Invalid client or client is not authorized\"")
- )
- }
- }
- }
|