blob: d70d3a6ff457cee7895606e21803e11cd2fc7439 [file] [log] [blame]
/*
* Copyright 2015-2016 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package whisk.core.controller.test
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import scala.concurrent.Await
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import spray.http.BasicHttpCredentials
import spray.http.StatusCodes._
import spray.routing.authentication.UserPass
import spray.routing.directives.AuthMagnet.fromContextAuthenticator
import whisk.common.TransactionCounter
import whisk.core.controller.Authenticate
import whisk.core.controller.AuthenticatedRoute
import whisk.core.entity._
import whisk.http.BasicHttpService
/**
* Tests authentication handler which guards API.
*
* Unit tests of the controller service as a standalone component.
* These tests exercise a fresh instance of the service object in memory -- these
* tests do NOT communication with a whisk deployment.
*
*
* @Idioglossia
* "using Specification DSL to write unit tests, as in should, must, not, be"
* "using Specs2RouteTest DSL to chain HTTP requests for unit testing, as in ~>"
*/
@RunWith(classOf[JUnitRunner])
class AuthenticateTests extends ControllerTestCommon with Authenticate {
behavior of "Authenticate"
it should "authorize a known user" in {
implicit val tid = transid()
val creds = createTempCredentials._1
val pass = UserPass(creds.uuid(), creds.key())
val user = Await.result(validateCredentials(Some(pass)), dbOpTimeout)
user.get should be(creds.toIdentity)
}
it should "authorize a known user from cache" in {
val creds = createTempCredentials(transid())._1
val pass = UserPass(creds.uuid(), creds.key())
// first query will be served from datastore
val stream = new ByteArrayOutputStream
val printstream = new PrintStream(stream)
val savedstream = authStore.outputStream
authStore.outputStream = printstream
try {
val user = Await.result(validateCredentials(Some(pass))(transid()), dbOpTimeout)
user.get should be(creds.toIdentity)
stream.toString should include regex (s"serving from datastore: ${creds.uuid()}")
stream.reset()
// repeat query, should be served from cache
val cachedUser = Await.result(validateCredentials(Some(pass))(transid()), dbOpTimeout)
cachedUser.get should be(creds.toIdentity)
stream.toString should include regex (s"serving from cache: ${creds.uuid()}")
stream.reset()
} finally {
authStore.outputStream = savedstream
stream.close()
printstream.close()
}
}
it should "not authorize a known user with an invalid key" in {
implicit val tid = transid()
val creds = createTempCredentials._1
val pass = UserPass(creds.uuid(), Secret().toString)
val user = Await.result(validateCredentials(Some(pass)), dbOpTimeout)
user should be(None)
}
it should "not authorize an unknown user" in {
implicit val tid = transid()
val creds = WhiskAuth(Subject(), AuthKey())
val pass = UserPass(creds.authkey.uuid(), creds.authkey.key())
val user = Await.result(validateCredentials(Some(pass)), dbOpTimeout)
user should be(None)
}
it should "not authorize when no user creds are provided" in {
implicit val tid = transid()
val user = Await.result(validateCredentials(None), dbOpTimeout)
user should be(None)
}
it should "not authorize when malformed user is provided" in {
implicit val tid = transid()
val pass = UserPass("x", Secret().toString)
val user = Await.result(validateCredentials(Some(pass)), dbOpTimeout)
user should be(None)
}
it should "not authorize when malformed secret is provided" in {
implicit val tid = transid()
val pass = UserPass(UUID().toString, "x")
val user = Await.result(validateCredentials(Some(pass)), dbOpTimeout)
user should be(None)
}
it should "not authorize when malformed creds are provided" in {
implicit val tid = transid()
val pass = UserPass("x", "y")
val user = Await.result(validateCredentials(Some(pass)), dbOpTimeout)
user should be(None)
}
}
class AuthenticatedRouteTests
extends ControllerTestCommon
with Authenticate
with AuthenticatedRoute
with TransactionCounter {
behavior of "Authenticated Route"
val route = sealRoute {
implicit val tid = transid()
handleRejections(BasicHttpService.customRejectionHandler) {
path("secured") {
authenticate(basicauth) {
user => complete("ok")
}
}
}
}
it should "authorize a known user" in {
val creds = createTempCredentials(transid())._1
val validCredentials = BasicHttpCredentials(creds.uuid(), creds.key())
Get("/secured") ~> addCredentials(validCredentials) ~> route ~> check {
status should be(OK)
}
}
it should "not authorize an unknown user" in {
val invalidCredentials = BasicHttpCredentials(UUID().toString, Secret().toString)
Get("/secured") ~> addCredentials(invalidCredentials) ~> route ~> check {
status should be(Unauthorized)
}
}
it should "report service unavailable when db lookup fails" in {
implicit val tid = transid()
val creds = createTempCredentials._1
// force another key for the same uuid to cause an internal violation
val secondCreds = WhiskAuth(Subject(), AuthKey(creds.uuid, Secret()))
put(authStore, secondCreds)
waitOnView(authStore, creds.uuid, 2)
val invalidCredentials = BasicHttpCredentials(creds.uuid(), creds.key())
Get("/secured") ~> addCredentials(invalidCredentials) ~> route ~> check {
status should be(InternalServerError)
}
}
}