From bf77f309c0ad2b1b6774d95b9d7327f6feb23c34 Mon Sep 17 00:00:00 2001 From: agrawalabhi Date: Fri, 11 Sep 2026 09:55:19 +0000 Subject: [PATCH 1/2] api, auth: add CallCredentials.allowedSecurityLevel() helper Add a protected helper on CallCredentials so that implementations can check whether the transport's security level meets the minimum level they require before transferring credentials, as described in gRFC L62. Use it in GoogleAuthLibraryCallCredentials, which previously compared against PRIVACY_AND_INTEGRITY for exact equality. The behavior is unchanged, since PRIVACY_AND_INTEGRITY is the highest security level. Supersedes #6616, which was approved but went stale. --- .../main/java/io/grpc/CallCredentials.java | 18 ++++ .../java/io/grpc/CallCredentialsTest.java | 82 +++++++++++++++++++ .../GoogleAuthLibraryCallCredentials.java | 2 +- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 api/src/test/java/io/grpc/CallCredentialsTest.java diff --git a/api/src/main/java/io/grpc/CallCredentials.java b/api/src/main/java/io/grpc/CallCredentials.java index eb92a6f15fa..f755078ff35 100644 --- a/api/src/main/java/io/grpc/CallCredentials.java +++ b/api/src/main/java/io/grpc/CallCredentials.java @@ -65,6 +65,24 @@ public abstract void applyRequestMetadata( public void thisUsesUnstableApi() { } + /** + * Determines whether the security level of the transport is higher than or equal to the minimum + * security level required to transfer these {@link CallCredentials}. + * + *

It is intended to be called from {@link #applyRequestMetadata} before sending any individual + * RPC. The credentials should not be sent if this method returns {@code false}. More details can + * be found in + * gRFC L62. + * + * @param requestInfo request-related information + * @param minSecurity minimum security level required by these {@code CallCredentials} + */ + protected static final boolean allowedSecurityLevel( + RequestInfo requestInfo, SecurityLevel minSecurity) { + return requestInfo.getSecurityLevel().compareTo(minSecurity) >= 0; + } + /** * The outlet of the produced headers. Not thread-safe. * diff --git a/api/src/test/java/io/grpc/CallCredentialsTest.java b/api/src/test/java/io/grpc/CallCredentialsTest.java new file mode 100644 index 00000000000..4e393aeef64 --- /dev/null +++ b/api/src/test/java/io/grpc/CallCredentialsTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2026 The gRPC Authors + * + * 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 io.grpc; + +import static com.google.common.truth.Truth.assertThat; + +import io.grpc.CallCredentials.RequestInfo; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link CallCredentials}. */ +@RunWith(JUnit4.class) +public class CallCredentialsTest { + + @Test + public void allowedSecurityLevel_higherOrEqualIsAllowed() { + assertThat( + CallCredentials.allowedSecurityLevel( + requestInfo(SecurityLevel.PRIVACY_AND_INTEGRITY), + SecurityLevel.PRIVACY_AND_INTEGRITY)) + .isTrue(); + assertThat( + CallCredentials.allowedSecurityLevel( + requestInfo(SecurityLevel.PRIVACY_AND_INTEGRITY), SecurityLevel.INTEGRITY)) + .isTrue(); + assertThat( + CallCredentials.allowedSecurityLevel( + requestInfo(SecurityLevel.INTEGRITY), SecurityLevel.NONE)) + .isTrue(); + } + + @Test + public void allowedSecurityLevel_lowerIsNotAllowed() { + assertThat( + CallCredentials.allowedSecurityLevel( + requestInfo(SecurityLevel.INTEGRITY), SecurityLevel.PRIVACY_AND_INTEGRITY)) + .isFalse(); + assertThat( + CallCredentials.allowedSecurityLevel( + requestInfo(SecurityLevel.NONE), SecurityLevel.INTEGRITY)) + .isFalse(); + } + + private static RequestInfo requestInfo(final SecurityLevel securityLevel) { + return new RequestInfo() { + @Override + public MethodDescriptor getMethodDescriptor() { + throw new UnsupportedOperationException(); + } + + @Override + public SecurityLevel getSecurityLevel() { + return securityLevel; + } + + @Override + public String getAuthority() { + throw new UnsupportedOperationException(); + } + + @Override + public Attributes getTransportAttrs() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java index b4b7f5b89e4..64383f7b437 100644 --- a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java +++ b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java @@ -98,7 +98,7 @@ public GoogleAuthLibraryCallCredentials(Credentials creds) { public void applyRequestMetadata( RequestInfo info, Executor appExecutor, final MetadataApplier applier) { SecurityLevel security = info.getSecurityLevel(); - if (requirePrivacy && security != SecurityLevel.PRIVACY_AND_INTEGRITY) { + if (requirePrivacy && !allowedSecurityLevel(info, SecurityLevel.PRIVACY_AND_INTEGRITY)) { applier.fail(Status.UNAUTHENTICATED .withDescription("Credentials require channel with PRIVACY_AND_INTEGRITY security level. " + "Observed security level: " + security)); From 3647efd702d46463c903bb0d9fc01b9def1e2084 Mon Sep 17 00:00:00 2001 From: agrawalabhi Date: Fri, 11 Sep 2026 10:07:01 +0000 Subject: [PATCH 2/2] api, auth: move allowedSecurityLevel coverage into the existing test Drop the newly added CallCredentialsTest in favor of extending the pre-existing GoogleAuthLibraryCallCredentialsTest, which already exercises the helper through the real code path. It covered PRIVACY_AND_INTEGRITY and INTEGRITY; add the remaining NONE case. --- .../java/io/grpc/CallCredentialsTest.java | 82 ------------------- .../GoogleAuthLibraryCallCredentialsTest.java | 16 ++++ 2 files changed, 16 insertions(+), 82 deletions(-) delete mode 100644 api/src/test/java/io/grpc/CallCredentialsTest.java diff --git a/api/src/test/java/io/grpc/CallCredentialsTest.java b/api/src/test/java/io/grpc/CallCredentialsTest.java deleted file mode 100644 index 4e393aeef64..00000000000 --- a/api/src/test/java/io/grpc/CallCredentialsTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2026 The gRPC Authors - * - * 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 io.grpc; - -import static com.google.common.truth.Truth.assertThat; - -import io.grpc.CallCredentials.RequestInfo; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link CallCredentials}. */ -@RunWith(JUnit4.class) -public class CallCredentialsTest { - - @Test - public void allowedSecurityLevel_higherOrEqualIsAllowed() { - assertThat( - CallCredentials.allowedSecurityLevel( - requestInfo(SecurityLevel.PRIVACY_AND_INTEGRITY), - SecurityLevel.PRIVACY_AND_INTEGRITY)) - .isTrue(); - assertThat( - CallCredentials.allowedSecurityLevel( - requestInfo(SecurityLevel.PRIVACY_AND_INTEGRITY), SecurityLevel.INTEGRITY)) - .isTrue(); - assertThat( - CallCredentials.allowedSecurityLevel( - requestInfo(SecurityLevel.INTEGRITY), SecurityLevel.NONE)) - .isTrue(); - } - - @Test - public void allowedSecurityLevel_lowerIsNotAllowed() { - assertThat( - CallCredentials.allowedSecurityLevel( - requestInfo(SecurityLevel.INTEGRITY), SecurityLevel.PRIVACY_AND_INTEGRITY)) - .isFalse(); - assertThat( - CallCredentials.allowedSecurityLevel( - requestInfo(SecurityLevel.NONE), SecurityLevel.INTEGRITY)) - .isFalse(); - } - - private static RequestInfo requestInfo(final SecurityLevel securityLevel) { - return new RequestInfo() { - @Override - public MethodDescriptor getMethodDescriptor() { - throw new UnsupportedOperationException(); - } - - @Override - public SecurityLevel getSecurityLevel() { - return securityLevel; - } - - @Override - public String getAuthority() { - throw new UnsupportedOperationException(); - } - - @Override - public Attributes getTransportAttrs() { - throw new UnsupportedOperationException(); - } - }; - } -} diff --git a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java index 75026fd7c18..38023363fc2 100644 --- a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java +++ b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java @@ -329,6 +329,22 @@ public void googleCredential_integrityDenied() { assertEquals(Status.Code.UNAUTHENTICATED, status.getCode()); } + @Test + public void googleCredential_noneDenied() { + final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE)); + final Credentials credentials = GoogleCredentials.create(token); + + GoogleAuthLibraryCallCredentials callCredentials = + new GoogleAuthLibraryCallCredentials(credentials); + callCredentials.applyRequestMetadata( + new RequestInfoImpl(SecurityLevel.NONE), executor, applier); + runPendingRunnables(); + + verify(applier).fail(statusCaptor.capture()); + Status status = statusCaptor.getValue(); + assertEquals(Status.Code.UNAUTHENTICATED, status.getCode()); + } + @Test public void serviceUri() throws Exception { GoogleAuthLibraryCallCredentials callCredentials =