Skip to content

Commit

Permalink
Added unit test for authorize (non-streamed)
Browse files Browse the repository at this point in the history
  • Loading branch information
tudor-da committed Oct 13, 2021
1 parent 5d22671 commit 36dfa78
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 9 deletions.
3 changes: 3 additions & 0 deletions ledger/ledger-api-auth/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ da_scala_test_suite(
],
deps = [
":ledger-api-auth",
"//ledger/error",
"@maven//:io_grpc_grpc_api",
"@maven//:io_grpc_grpc_context",
"@maven//:org_scalatest_scalatest_compatible",
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,7 @@ final class Authorizer(
call,
)

private def assertServerCall[A](
observer: StreamObserver[A]
): ServerCallStreamObserver[A] =
private def assertServerCall[A](observer: StreamObserver[A]): ServerCallStreamObserver[A] =
observer match {
case _: ServerCallStreamObserver[_] =>
observer.asInstanceOf[ServerCallStreamObserver[A]]
Expand All @@ -190,14 +188,15 @@ final class Authorizer(

private def authenticatedClaimsFromContext()(implicit
loggingContext: LoggingContext
): Try[ClaimSet.Claims] =
): Try[ClaimSet.Claims] = {
AuthorizationInterceptor
.extractClaimSetFromContext()
.fold[Try[ClaimSet.Claims]](Failure(unauthenticated())) {
case ClaimSet.Unauthenticated =>
Failure(unauthenticated())
case claims: ClaimSet.Claims => Success(claims)
}
}

private def authorize[Req, Res](call: (Req, ServerCallStreamObserver[Res]) => Unit)(
authorized: ClaimSet.Claims => Either[AuthorizationError, Unit]
Expand Down Expand Up @@ -229,10 +228,10 @@ final class Authorizer(
)
}

private def authorize[Req, Res](call: Req => Future[Res])(
private[auth] def authorize[Req, Res](call: Req => Future[Res])(
authorized: ClaimSet.Claims => Either[AuthorizationError, Unit]
): Req => Future[Res] = LoggingContext.newLoggingContext {
implicit loggingContext: LoggingContext => request =>
): Req => Future[Res] = request =>
LoggingContext.newLoggingContext { implicit loggingContext =>
authenticatedClaimsFromContext()
.fold(
ex => {
Expand All @@ -249,7 +248,7 @@ final class Authorizer(
Future.failed(permissionDenied(authorizationError.reason))
},
)
}
}

private def permissionDenied(
cause: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ final class AuthorizationInterceptor(protected val authService: AuthService, ec:

object AuthorizationInterceptor {

private val contextKeyClaimSet = Context.key[ClaimSet]("AuthServiceDecodedClaim")
private[auth] val contextKeyClaimSet = Context.key[ClaimSet]("AuthServiceDecodedClaim")

def extractClaimSetFromContext(): Option[ClaimSet] =
Option(contextKeyClaimSet.get())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.ledger.api.auth

import com.daml.error.ErrorCodesVersionSwitcher
import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor
import io.grpc.{Status, StatusRuntimeException}
import org.scalatest.Assertion
import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers

import java.time.Instant
import scala.concurrent.Future
import scala.util.{Failure, Success, Try}

class AuthorizerSpec extends AsyncFlatSpec with Matchers {
private val className = classOf[Authorizer].getSimpleName
private val dummyRequest = 1337L
private val expectedSuccessfulResponse = "leet"
private val dummyReqRes: Long => Future[String] =
Map(dummyRequest -> Future.successful(expectedSuccessfulResponse))
private val allAuthorized: ClaimSet.Claims => Either[AuthorizationError, Unit] = _ => Right(())
private val unauthorized: ClaimSet.Claims => Either[AuthorizationError, Unit] = _ =>
Left(AuthorizationError.MissingAdminClaim)

behavior of s"$className.authorize"

it should "authorize if claims are valid" in {
contextWithClaims {
authorizer(false).authorize(dummyReqRes)(allAuthorized)(dummyRequest)
}.map(_ shouldBe expectedSuccessfulResponse)
}

behavior of s"$className.authorize (V1 error codes)"

it should "return unauthenticated if missing claims" in {
testUnauthenticated(false)
}

it should "return permission denied on authorization error" in {
testPermissionDenied(false)
}

behavior of s"$className.authorize (V2 error codes)"

it should "return unauthenticated if missing claims" in {
testUnauthenticated(true)
}

it should "return permission denied on authorization error" in {
testPermissionDenied(true)
}

private def testPermissionDenied(selfServiceErrorCodes: Boolean) =
contextWithClaims {
authorizer(selfServiceErrorCodes).authorize(dummyReqRes)(unauthorized)(dummyRequest)
}
.transform(
assertExpectedFailure(selfServiceErrorCodes = selfServiceErrorCodes)(
Status.PERMISSION_DENIED.getCode
)
)

private def testUnauthenticated(selfServiceErrorCodes: Boolean) =
contextWithoutClaims {
authorizer(selfServiceErrorCodes).authorize(dummyReqRes)(allAuthorized)(dummyRequest)
}
.transform(
assertExpectedFailure(selfServiceErrorCodes = selfServiceErrorCodes)(
Status.UNAUTHENTICATED.getCode
)
)

private def assertExpectedFailure[T](
selfServiceErrorCodes: Boolean
)(expectedStatusCode: Status.Code): Try[T] => Try[Assertion] = {
case Failure(ex: StatusRuntimeException) =>
ex.getStatus.getCode shouldBe expectedStatusCode
if (selfServiceErrorCodes) {
ex.getStatus.getDescription shouldBe "An error occurred. Please contact the operator and inquire about the request <no-correlation-id>"
}
Success(succeed)
case ex => fail(s"Expected a failure with StatusRuntimeException but got $ex")
}

private def contextWithoutClaims[R](f: => R): R = io.grpc.Context.ROOT.call(() => f)

private def contextWithClaims[R](f: => R): R =
io.grpc.Context.ROOT
.withValue(AuthorizationInterceptor.contextKeyClaimSet, ClaimSet.Claims.Wildcard)
.call(() => f)

private def authorizer(selfServiceErrorCodes: Boolean) = new Authorizer(
() => Instant.ofEpochSecond(1337L),
"some-ledger-id",
"participant-id",
new ErrorCodesVersionSwitcher(selfServiceErrorCodes),
)
}

0 comments on commit 36dfa78

Please sign in to comment.