Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions alts/src/main/java/io/grpc/alts/AuthorizationUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2019 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.alts;

import io.grpc.ServerCall;
import io.grpc.Status;
import io.grpc.alts.internal.AltsAuthContext;
import io.grpc.alts.internal.AltsProtocolNegotiator;
import java.util.Collection;

/** Utility class for ALTS client authorization. */
public final class AuthorizationUtil {

private AuthorizationUtil() {}

/**
* Given a server call, performs client authorization check, i.e., checks if the client service
* account matches one of the expected service accounts. It returns OK if client is authorized and
* an error otherwise.
*/
public static Status clientAuthorizationCheck(
ServerCall<?, ?> call, Collection<String> expectedServiceAccounts) {
AltsAuthContext altsContext =
(AltsAuthContext) call.getAttributes().get(AltsProtocolNegotiator.AUTH_CONTEXT_KEY);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't AUTH_CONTEXT_KEY a Attributes.Key<AltsAuthContext>?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This because extractPeerObject() returns an Object, rather than AltAuthContext. Internally, we have a different AuthContext class. We need to use share the same API for returning the peer object. Let me know if you have better ideas.

if (altsContext == null) {
return Status.NOT_FOUND.withDescription("Peer ALTS AuthContext not found");
}
if (expectedServiceAccounts.contains(altsContext.getPeerServiceAccount())) {
return Status.OK;
}
return Status.PERMISSION_DENIED.withDescription(
"Client " + altsContext.getPeerServiceAccount() + " is not authorized");
}
}
114 changes: 114 additions & 0 deletions alts/src/test/java/io/grpc/alts/AuthorizationUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2019 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.alts;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.collect.Lists;
import io.grpc.Attributes;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.ServerCall;
import io.grpc.Status;
import io.grpc.alts.internal.AltsAuthContext;
import io.grpc.alts.internal.AltsProtocolNegotiator;
import io.grpc.alts.internal.HandshakerResult;
import io.grpc.alts.internal.Identity;
import javax.annotation.Nullable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Unit tests for {@link AuthorizationUtil}. */
@RunWith(JUnit4.class)
public final class AuthorizationUtilTest {

@Test
public void altsAuthorizationCheck() throws Exception {
Status status =
AuthorizationUtil.clientAuthorizationCheck(
new FakeServerCall(null), Lists.newArrayList("Alice"));
assertThat(status.getCode()).isEqualTo(Status.Code.NOT_FOUND);
assertThat(status.getDescription()).startsWith("Peer ALTS AuthContext not found");
status =
AuthorizationUtil.clientAuthorizationCheck(
new FakeServerCall("Alice"), Lists.newArrayList("Alice", "Bob"));
assertThat(status.getCode()).isEqualTo(Status.Code.OK);
status =
AuthorizationUtil.clientAuthorizationCheck(
new FakeServerCall("Alice"), Lists.newArrayList("Bob", "Joe"));
assertThat(status.getCode()).isEqualTo(Status.Code.PERMISSION_DENIED);
assertThat(status.getDescription()).endsWith("not authorized");
}

private static class FakeServerCall extends ServerCall<String, String> {
final Attributes attrs;

FakeServerCall(@Nullable String peerServiceAccount) {
Attributes.Builder attrsBuilder = Attributes.newBuilder();
if (peerServiceAccount != null) {
HandshakerResult handshakerResult =
HandshakerResult.newBuilder()
.setPeerIdentity(Identity.newBuilder().setServiceAccount(peerServiceAccount))
.build();
AltsAuthContext altsAuthContext = new AltsAuthContext(handshakerResult);
attrsBuilder.set(AltsProtocolNegotiator.AUTH_CONTEXT_KEY, altsAuthContext);
}
attrs = attrsBuilder.build();
}

@Override
public void request(int numMessages) {
throw new AssertionError("Should not be called");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A trick you can do for these is to extend ForwardingServerCall and then implement delegate() to throw like you're doing here. This is fine, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack on the trick. Let's keep the current FakeServerCall if you don't mind.

}

@Override
public void sendHeaders(Metadata headers) {
throw new AssertionError("Should not be called");
}

@Override
public void sendMessage(String message) {
throw new AssertionError("Should not be called");
}

@Override
public void close(Status status, Metadata trailers) {
throw new AssertionError("Should not be called");
}

@Override
public boolean isCancelled() {
throw new AssertionError("Should not be called");
}

@Override
public Attributes getAttributes() {
return attrs;
}

@Override
public String getAuthority() {
throw new AssertionError("Should not be called");
}

@Override
public MethodDescriptor<String, String> getMethodDescriptor() {
throw new AssertionError("Should not be called");
}
}
}