| /* |
| * 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) |
| } |
| } |
| |
| } |