diff --git a/src/main/kotlin/org/wfanet/measurement/access/service/Errors.kt b/src/main/kotlin/org/wfanet/measurement/access/service/Errors.kt index bceaf6779b7..5e9373a246b 100644 --- a/src/main/kotlin/org/wfanet/measurement/access/service/Errors.kt +++ b/src/main/kotlin/org/wfanet/measurement/access/service/Errors.kt @@ -173,6 +173,40 @@ class PrincipalTypeNotSupportedException( cause, ) +class PrincipalNotFoundForUserException(issuer: String, subject: String, cause: Throwable? = null) : + ServiceException( + Errors.Reason.PRINCIPAL_NOT_FOUND_FOR_USER, + "Principal not found for user with issuer $issuer and subject $subject", + mapOf(Errors.Metadata.ISSUER to issuer, Errors.Metadata.SUBJECT to subject), + cause, + ) + +class PrincipalNotFoundForTlsClientException( + authorityKeyIdentifier: String, + cause: Throwable? = null, +) : + ServiceException( + reason, + "Principal not found for tls client with authority key identifier $authorityKeyIdentifier", + mapOf(Errors.Metadata.AUTHORITY_KEY_IDENTIFIER to authorityKeyIdentifier), + cause, + ) { + companion object : Factory() { + override val reason: Errors.Reason + get() = Errors.Reason.PRINCIPAL_NOT_FOUND_FOR_TLS_CLIENT + + override fun fromInternal( + internalMetadata: Map, + cause: Throwable, + ): PrincipalNotFoundForTlsClientException { + return PrincipalNotFoundForTlsClientException( + internalMetadata.getValue(InternalErrors.Metadata.AUTHORITY_KEY_IDENTIFIER), + cause, + ) + } + } +} + class PermissionNotFoundException(name: String, cause: Throwable? = null) : ServiceException( reason, diff --git a/src/main/kotlin/org/wfanet/measurement/access/service/v1alpha/PrincipalsService.kt b/src/main/kotlin/org/wfanet/measurement/access/service/v1alpha/PrincipalsService.kt index 979450ef76c..1e8c2539883 100644 --- a/src/main/kotlin/org/wfanet/measurement/access/service/v1alpha/PrincipalsService.kt +++ b/src/main/kotlin/org/wfanet/measurement/access/service/v1alpha/PrincipalsService.kt @@ -16,24 +16,31 @@ package org.wfanet.measurement.access.service.v1alpha +import com.google.protobuf.Empty import io.grpc.Status import io.grpc.StatusException import org.wfanet.measurement.access.service.InvalidFieldValueException import org.wfanet.measurement.access.service.PrincipalAlreadyExistsException import org.wfanet.measurement.access.service.PrincipalKey import org.wfanet.measurement.access.service.PrincipalNotFoundException +import org.wfanet.measurement.access.service.PrincipalNotFoundForTlsClientException +import org.wfanet.measurement.access.service.PrincipalNotFoundForUserException import org.wfanet.measurement.access.service.PrincipalTypeNotSupportedException import org.wfanet.measurement.access.service.RequiredFieldNotSetException import org.wfanet.measurement.access.service.internal.Errors as InternalErrors import org.wfanet.measurement.access.v1alpha.CreatePrincipalRequest +import org.wfanet.measurement.access.v1alpha.DeletePrincipalRequest import org.wfanet.measurement.access.v1alpha.GetPrincipalRequest +import org.wfanet.measurement.access.v1alpha.LookupPrincipalRequest import org.wfanet.measurement.access.v1alpha.Principal import org.wfanet.measurement.access.v1alpha.PrincipalsGrpcKt import org.wfanet.measurement.common.api.ResourceIds import org.wfanet.measurement.internal.access.Principal as InternalPrincipal import org.wfanet.measurement.internal.access.PrincipalsGrpcKt.PrincipalsCoroutineStub as InternalPrincipalsCoroutineStub import org.wfanet.measurement.internal.access.createUserPrincipalRequest as internalCreateUserPrincipalRequest +import org.wfanet.measurement.internal.access.deletePrincipalRequest as internalDeletePrincipalRequest import org.wfanet.measurement.internal.access.getPrincipalRequest as internalGetPrincipalRequest +import org.wfanet.measurement.internal.access.lookupPrincipalRequest as internalLookupPrincipalRequest class PrincipalsService(private val internalPrincipalsStub: InternalPrincipalsCoroutineStub) : PrincipalsGrpcKt.PrincipalsCoroutineImplBase() { @@ -147,4 +154,115 @@ class PrincipalsService(private val internalPrincipalsStub: InternalPrincipalsCo return internalResponse.toPrincipal() } + + override suspend fun deletePrincipal(request: DeletePrincipalRequest): Empty { + if (request.name.isEmpty()) { + throw RequiredFieldNotSetException("name") + .asStatusRuntimeException(Status.Code.INVALID_ARGUMENT) + } + val principalKey = + PrincipalKey.fromName(request.name) + ?: throw InvalidFieldValueException("name") + .asStatusRuntimeException(Status.Code.INVALID_ARGUMENT) + + try { + internalPrincipalsStub.deletePrincipal( + internalDeletePrincipalRequest { principalResourceId = principalKey.principalId } + ) + } catch (e: StatusException) { + throw when (InternalErrors.getReason(e)) { + InternalErrors.Reason.PRINCIPAL_NOT_FOUND -> + PrincipalNotFoundException(request.name, e) + .asStatusRuntimeException(Status.Code.NOT_FOUND) + InternalErrors.Reason.PRINCIPAL_TYPE_NOT_SUPPORTED -> + PrincipalTypeNotSupportedException(Principal.IdentityCase.TLS_CLIENT, e) + .asStatusRuntimeException(Status.Code.FAILED_PRECONDITION) + InternalErrors.Reason.PRINCIPAL_ALREADY_EXISTS, + InternalErrors.Reason.PRINCIPAL_NOT_FOUND_FOR_USER, + InternalErrors.Reason.PRINCIPAL_NOT_FOUND_FOR_TLS_CLIENT, + InternalErrors.Reason.PERMISSION_NOT_FOUND, + InternalErrors.Reason.PERMISSION_NOT_FOUND_FOR_ROLE, + InternalErrors.Reason.ROLE_NOT_FOUND, + InternalErrors.Reason.ROLE_ALREADY_EXISTS, + InternalErrors.Reason.POLICY_NOT_FOUND, + InternalErrors.Reason.POLICY_NOT_FOUND_FOR_PROTECTED_RESOURCE, + InternalErrors.Reason.POLICY_ALREADY_EXISTS, + InternalErrors.Reason.POLICY_BINDING_MEMBERSHIP_ALREADY_EXISTS, + InternalErrors.Reason.POLICY_BINDING_MEMBERSHIP_NOT_FOUND, + InternalErrors.Reason.RESOURCE_TYPE_NOT_FOUND_IN_PERMISSION, + InternalErrors.Reason.REQUIRED_FIELD_NOT_SET, + InternalErrors.Reason.INVALID_FIELD_VALUE, + InternalErrors.Reason.ETAG_MISMATCH, + null -> Status.INTERNAL.withCause(e).asRuntimeException() + } + } + + return Empty.getDefaultInstance() + } + + override suspend fun lookupPrincipal(request: LookupPrincipalRequest): Principal { + when (request.lookupKeyCase) { + LookupPrincipalRequest.LookupKeyCase.USER -> { + if (request.user.issuer.isEmpty()) { + throw RequiredFieldNotSetException("user.issuer") + .asStatusRuntimeException(Status.Code.INVALID_ARGUMENT) + } + + if (request.user.subject.isEmpty()) { + throw RequiredFieldNotSetException("user.subject") + .asStatusRuntimeException(Status.Code.INVALID_ARGUMENT) + } + } + LookupPrincipalRequest.LookupKeyCase.TLS_CLIENT -> { + if (request.tlsClient.authorityKeyIdentifier.isEmpty) { + throw RequiredFieldNotSetException("tlsclient.authoritykeyidentifier") + .asStatusRuntimeException(Status.Code.INVALID_ARGUMENT) + } + } + else -> + throw RequiredFieldNotSetException("lookup_key") + .asStatusRuntimeException(Status.Code.INVALID_ARGUMENT) + } + + val internalResponse: InternalPrincipal = + try { + internalPrincipalsStub.lookupPrincipal( + internalLookupPrincipalRequest { + if (request.lookupKeyCase == LookupPrincipalRequest.LookupKeyCase.USER) { + user = request.user.toInternal() + } else { + tlsClient = request.tlsClient.toInternal() + } + } + ) + } catch (e: StatusException) { + throw when (InternalErrors.getReason(e)) { + InternalErrors.Reason.PRINCIPAL_NOT_FOUND_FOR_USER -> + PrincipalNotFoundForUserException(request.user.issuer, request.user.subject, e) + .asStatusRuntimeException(Status.Code.NOT_FOUND) + InternalErrors.Reason.PRINCIPAL_NOT_FOUND_FOR_TLS_CLIENT -> + PrincipalNotFoundForTlsClientException.fromInternal(e) + .asStatusRuntimeException(e.status.code) + InternalErrors.Reason.PRINCIPAL_ALREADY_EXISTS, + InternalErrors.Reason.PRINCIPAL_NOT_FOUND, + InternalErrors.Reason.PRINCIPAL_TYPE_NOT_SUPPORTED, + InternalErrors.Reason.PERMISSION_NOT_FOUND, + InternalErrors.Reason.PERMISSION_NOT_FOUND_FOR_ROLE, + InternalErrors.Reason.ROLE_NOT_FOUND, + InternalErrors.Reason.ROLE_ALREADY_EXISTS, + InternalErrors.Reason.POLICY_NOT_FOUND, + InternalErrors.Reason.POLICY_NOT_FOUND_FOR_PROTECTED_RESOURCE, + InternalErrors.Reason.POLICY_ALREADY_EXISTS, + InternalErrors.Reason.POLICY_BINDING_MEMBERSHIP_ALREADY_EXISTS, + InternalErrors.Reason.POLICY_BINDING_MEMBERSHIP_NOT_FOUND, + InternalErrors.Reason.RESOURCE_TYPE_NOT_FOUND_IN_PERMISSION, + InternalErrors.Reason.REQUIRED_FIELD_NOT_SET, + InternalErrors.Reason.INVALID_FIELD_VALUE, + InternalErrors.Reason.ETAG_MISMATCH, + null -> Status.INTERNAL.withCause(e).asRuntimeException() + } + } + + return internalResponse.toPrincipal() + } } diff --git a/src/main/kotlin/org/wfanet/measurement/access/service/v1alpha/ResourceConversion.kt b/src/main/kotlin/org/wfanet/measurement/access/service/v1alpha/ResourceConversion.kt index 85efda83793..5867fcc331a 100644 --- a/src/main/kotlin/org/wfanet/measurement/access/service/v1alpha/ResourceConversion.kt +++ b/src/main/kotlin/org/wfanet/measurement/access/service/v1alpha/ResourceConversion.kt @@ -27,6 +27,7 @@ import org.wfanet.measurement.access.v1alpha.principal import org.wfanet.measurement.access.v1alpha.role import org.wfanet.measurement.internal.access.Principal as InternalPrincipal import org.wfanet.measurement.internal.access.PrincipalKt.oAuthUser as internalOAuthUser +import org.wfanet.measurement.internal.access.PrincipalKt.tlsClient as internalTlsClient import org.wfanet.measurement.internal.access.Role as InternalRole fun InternalPrincipal.toPrincipal(): Principal { @@ -72,3 +73,8 @@ fun Principal.OAuthUser.toInternal(): InternalPrincipal.OAuthUser { subject = source.subject } } + +fun Principal.TlsClient.toInternal(): InternalPrincipal.TlsClient { + val source = this + return internalTlsClient { authorityKeyIdentifier = source.authorityKeyIdentifier } +} diff --git a/src/main/proto/wfa/measurement/access/v1alpha/principals_service.proto b/src/main/proto/wfa/measurement/access/v1alpha/principals_service.proto index f787bac1e43..a5ad12b7da7 100644 --- a/src/main/proto/wfa/measurement/access/v1alpha/principals_service.proto +++ b/src/main/proto/wfa/measurement/access/v1alpha/principals_service.proto @@ -47,11 +47,19 @@ service Principals { // Deletes a `Principal`. // // This will also remove the `Principal` from all `Policy` bindings. + // + // Error reasons: + // * `PRINCIPAL_NOT_FOUND` + // * `PRINCIPAL_TYPE_NOT_SUPPORTED` rpc DeletePrincipal(DeletePrincipalRequest) returns (google.protobuf.Empty) { option (google.api.method_signature) = "name"; } // Looks up a `Principal` by lookup key. + // + // Error reasons: + // * `PRINCIPAL_NOT_FOUND_FOR_USER` + // * `PRINCIPAL_NOT_FOUND_FOR_TLS_CLIENT` rpc LookupPrincipal(LookupPrincipalRequest) returns (Principal); } diff --git a/src/test/kotlin/org/wfanet/measurement/access/service/v1alpha/PrincipalsServiceTest.kt b/src/test/kotlin/org/wfanet/measurement/access/service/v1alpha/PrincipalsServiceTest.kt index e26e557c8d7..d4a7d33b6f9 100644 --- a/src/test/kotlin/org/wfanet/measurement/access/service/v1alpha/PrincipalsServiceTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/access/service/v1alpha/PrincipalsServiceTest.kt @@ -18,6 +18,7 @@ package org.wfanet.measurement.access.service.v1alpha import com.google.common.truth.Truth.assertThat import com.google.common.truth.extensions.proto.ProtoTruth.assertThat +import com.google.protobuf.Empty import com.google.protobuf.kotlin.toByteStringUtf8 import com.google.rpc.errorInfo import io.grpc.Status @@ -36,19 +37,28 @@ import org.mockito.kotlin.stub import org.wfanet.measurement.access.service.Errors import org.wfanet.measurement.access.service.internal.PrincipalAlreadyExistsException import org.wfanet.measurement.access.service.internal.PrincipalNotFoundException +import org.wfanet.measurement.access.service.internal.PrincipalNotFoundForTlsClientException +import org.wfanet.measurement.access.service.internal.PrincipalNotFoundForUserException +import org.wfanet.measurement.access.service.internal.PrincipalTypeNotSupportedException +import org.wfanet.measurement.access.v1alpha.DeletePrincipalRequest import org.wfanet.measurement.access.v1alpha.GetPrincipalRequest import org.wfanet.measurement.access.v1alpha.PrincipalKt import org.wfanet.measurement.access.v1alpha.createPrincipalRequest +import org.wfanet.measurement.access.v1alpha.deletePrincipalRequest import org.wfanet.measurement.access.v1alpha.getPrincipalRequest +import org.wfanet.measurement.access.v1alpha.lookupPrincipalRequest import org.wfanet.measurement.access.v1alpha.principal import org.wfanet.measurement.common.grpc.errorInfo import org.wfanet.measurement.common.grpc.testing.GrpcTestServerRule import org.wfanet.measurement.common.grpc.testing.mockService import org.wfanet.measurement.common.testing.verifyProtoArgument +import org.wfanet.measurement.internal.access.Principal import org.wfanet.measurement.internal.access.PrincipalKt as InternalPrincipalKt import org.wfanet.measurement.internal.access.PrincipalsGrpcKt as InternalPrincipalsGrpcKt import org.wfanet.measurement.internal.access.createUserPrincipalRequest as internalCreateUserPrincipalRequest +import org.wfanet.measurement.internal.access.deletePrincipalRequest as internalDeletePrincipalRequest import org.wfanet.measurement.internal.access.getPrincipalRequest as internalGetPrincipalRequest +import org.wfanet.measurement.internal.access.lookupPrincipalRequest as internalLookupPrincipalRequest import org.wfanet.measurement.internal.access.principal as internalPrincipal @RunWith(JUnit4::class) @@ -437,4 +447,325 @@ class PrincipalsServiceTest { } ) } + + @Test + fun `deletePrincipal returns empty`() = runBlocking { + internalServiceMock.stub { + onBlocking { deletePrincipal(any()) } doReturn Empty.getDefaultInstance() + } + + val request = deletePrincipalRequest { name = "principals/user-1" } + val response = service.deletePrincipal(request) + + verifyProtoArgument( + internalServiceMock, + InternalPrincipalsGrpcKt.PrincipalsCoroutineImplBase::deletePrincipal, + ) + .isEqualTo(internalDeletePrincipalRequest { principalResourceId = "user-1" }) + + assertThat(response).isEqualTo(Empty.getDefaultInstance()) + } + + @Test + fun `deletePrincipal throws PRINCIPAL_NOT_FOUND from backend`() = runBlocking { + internalServiceMock.stub { + onBlocking { deletePrincipal(any()) } doThrow + PrincipalNotFoundException("user-1").asStatusRuntimeException(Status.Code.NOT_FOUND) + } + + val request = deletePrincipalRequest { name = "principals/user-1" } + val exception = assertFailsWith { service.deletePrincipal(request) } + + assertThat(exception.status.code).isEqualTo(Status.Code.NOT_FOUND) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.PRINCIPAL_NOT_FOUND.name + metadata[Errors.Metadata.PRINCIPAL.key] = request.name + } + ) + } + + @Test + fun `deletePrincipal throws PRINCIPAL_TYPE_NOT_SUPPORTED from backend`() = runBlocking { + internalServiceMock.stub { + onBlocking { deletePrincipal(any()) } doThrow + PrincipalTypeNotSupportedException("user-1", Principal.IdentityCase.TLS_CLIENT) + .asStatusRuntimeException(Status.Code.FAILED_PRECONDITION) + } + + val request = deletePrincipalRequest { name = "principals/user-1" } + val exception = assertFailsWith { service.deletePrincipal(request) } + + assertThat(exception.status.code).isEqualTo(Status.Code.FAILED_PRECONDITION) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.PRINCIPAL_TYPE_NOT_SUPPORTED.name + metadata[Errors.Metadata.PRINCIPAL_TYPE.key] = Principal.IdentityCase.TLS_CLIENT.name + } + ) + } + + @Test + fun `deletePrincipal throws REQUIRED_FIELD_NOT_SET when name is not set`() = runBlocking { + val exception = + assertFailsWith { + service.deletePrincipal(DeletePrincipalRequest.getDefaultInstance()) + } + + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.REQUIRED_FIELD_NOT_SET.name + metadata[Errors.Metadata.FIELD_NAME.key] = "name" + } + ) + } + + @Test + fun `deletePrincipal throws INVALID_FIELD_VALUE when name is malformed`() = runBlocking { + val exception = + assertFailsWith { + service.deletePrincipal(deletePrincipalRequest { name = "principles/user-1" }) + } + + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.INVALID_FIELD_VALUE.name + metadata[Errors.Metadata.FIELD_NAME.key] = "name" + } + ) + } + + @Test + fun `lookupPrincipal returns user Principal`() = runBlocking { + val internalPrincipal = internalPrincipal { + principalResourceId = "user-1" + user = + InternalPrincipalKt.oAuthUser { + issuer = "example.com" + subject = "user1@example.com" + } + } + internalServiceMock.stub { onBlocking { lookupPrincipal(any()) } doReturn internalPrincipal } + + val request = lookupPrincipalRequest { + user = + PrincipalKt.oAuthUser { + issuer = "example.com" + subject = "user1@example.com" + } + } + val response = service.lookupPrincipal(request) + + verifyProtoArgument( + internalServiceMock, + InternalPrincipalsGrpcKt.PrincipalsCoroutineImplBase::lookupPrincipal, + ) + .isEqualTo(internalLookupPrincipalRequest { user = internalPrincipal.user }) + + assertThat(response) + .isEqualTo( + principal { + name = "principals/${internalPrincipal.principalResourceId}" + user = + PrincipalKt.oAuthUser { + issuer = internalPrincipal.user.issuer + subject = internalPrincipal.user.subject + } + } + ) + } + + @Test + fun `lookupPrincipal returns tls client Principal`() = runBlocking { + val internalPrincipal = internalPrincipal { + principalResourceId = "user-1" + tlsClient = + InternalPrincipalKt.tlsClient { authorityKeyIdentifier = "akid".toByteStringUtf8() } + } + internalServiceMock.stub { onBlocking { lookupPrincipal(any()) } doReturn internalPrincipal } + + val request = lookupPrincipalRequest { + tlsClient = PrincipalKt.tlsClient { authorityKeyIdentifier = "akid".toByteStringUtf8() } + } + val response = service.lookupPrincipal(request) + + verifyProtoArgument( + internalServiceMock, + InternalPrincipalsGrpcKt.PrincipalsCoroutineImplBase::lookupPrincipal, + ) + .isEqualTo(internalLookupPrincipalRequest { tlsClient = internalPrincipal.tlsClient }) + + assertThat(response) + .isEqualTo( + principal { + name = "principals/${internalPrincipal.principalResourceId}" + tlsClient = PrincipalKt.tlsClient { authorityKeyIdentifier = "akid".toByteStringUtf8() } + } + ) + } + + @Test + fun `lookupPrincipal throws REQUIRED_FIELD_NOT_SET when principle user is not set`() = + runBlocking { + val exception = + assertFailsWith { + service.lookupPrincipal(lookupPrincipalRequest {}) + } + + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.REQUIRED_FIELD_NOT_SET.name + metadata[Errors.Metadata.FIELD_NAME.key] = "lookup_key" + } + ) + } + + @Test + fun `lookupPrincipal throws REQUIRED_FIELD_NOT_SET when principal user issuer is not set`() = + runBlocking { + val exception = + assertFailsWith { + service.lookupPrincipal( + lookupPrincipalRequest { + user = PrincipalKt.oAuthUser { subject = "user1@example.com" } + } + ) + } + + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.REQUIRED_FIELD_NOT_SET.name + metadata[Errors.Metadata.FIELD_NAME.key] = "user.issuer" + } + ) + } + + @Test + fun `lookupPrincipal throws REQUIRED_FIELD_NOT_SET when principal user subject is not set`() = + runBlocking { + val exception = + assertFailsWith { + service.lookupPrincipal( + lookupPrincipalRequest { user = PrincipalKt.oAuthUser { issuer = "example.com" } } + ) + } + + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.REQUIRED_FIELD_NOT_SET.name + metadata[Errors.Metadata.FIELD_NAME.key] = "user.subject" + } + ) + } + + @Test + fun `lookupPrincipal throws REQUIRED_FIELD_NOT_SET when principal tls client is not set`() = + runBlocking { + val exception = + assertFailsWith { + service.lookupPrincipal(lookupPrincipalRequest {}) + } + + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.REQUIRED_FIELD_NOT_SET.name + metadata[Errors.Metadata.FIELD_NAME.key] = "lookup_key" + } + ) + } + + @Test + fun `lookupPrincipal throws REQUIRED_FIELD_NOT_SET when principal tls client authority key identifier is not set`() = + runBlocking { + val exception = + assertFailsWith { + service.lookupPrincipal(lookupPrincipalRequest { tlsClient = PrincipalKt.tlsClient {} }) + } + + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.REQUIRED_FIELD_NOT_SET.name + metadata[Errors.Metadata.FIELD_NAME.key] = "tlsclient.authoritykeyidentifier" + } + ) + } + + @Test + fun `lookupPrincipal throws PRINCIPAL_NOT_FOUND_FOR_USER from backend`() = runBlocking { + internalServiceMock.stub { + onBlocking { lookupPrincipal(any()) } doThrow + PrincipalNotFoundForUserException(subject = "user1@example.com", issuer = "exmaple.com") + .asStatusRuntimeException(Status.Code.NOT_FOUND) + } + + val request = lookupPrincipalRequest { + user = + PrincipalKt.oAuthUser { + issuer = "example.com" + subject = "user1@example.com" + } + } + val exception = assertFailsWith { service.lookupPrincipal(request) } + + assertThat(exception.status.code).isEqualTo(Status.Code.NOT_FOUND) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.PRINCIPAL_NOT_FOUND_FOR_USER.name + metadata[Errors.Metadata.ISSUER.key] = request.user.issuer + metadata[Errors.Metadata.SUBJECT.key] = request.user.subject + } + ) + } + + @Test + fun `lookupPrincipal throws PRINCIPAL_NOT_FOUND_FOR_TLS_CLIENT from backend`() = runBlocking { + internalServiceMock.stub { + onBlocking { lookupPrincipal(any()) } doThrow + PrincipalNotFoundForTlsClientException(authorityKeyIdentifier = "akid".toByteStringUtf8()) + .asStatusRuntimeException(Status.Code.NOT_FOUND) + } + + val request = lookupPrincipalRequest { + tlsClient = PrincipalKt.tlsClient { authorityKeyIdentifier = "akid".toByteStringUtf8() } + } + + val exception = assertFailsWith { service.lookupPrincipal(request) } + + assertThat(exception.status.code).isEqualTo(Status.Code.NOT_FOUND) + assertThat(exception.errorInfo) + .isEqualTo( + errorInfo { + domain = Errors.DOMAIN + reason = Errors.Reason.PRINCIPAL_NOT_FOUND_FOR_TLS_CLIENT.name + metadata[Errors.Metadata.AUTHORITY_KEY_IDENTIFIER.key] = "61:6B:69:64" + } + ) + } }