Skip to content

Commit 37f181c

Browse files
Yanniccopybara-github
authored andcommitted
[credentialhelper] Add types to communicate with the subprocess
This change adds `GetCredentials{Request,Response}`, which we'll use to pass data in `stdin` to the credental helper subprocess, and read the response from `stdout` of the subprocess. Progress on https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md#proposal Closes bazelbuild#15803. PiperOrigin-RevId: 459701706 Change-Id: Icbd9fc491546ee5599d5a9d04680671b06a91a85
1 parent 4fca652 commit 37f181c

File tree

6 files changed

+534
-0
lines changed

6 files changed

+534
-0
lines changed

src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD

+2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ java_library(
1515
srcs = glob(["*.java"]),
1616
deps = [
1717
"//src/main/java/com/google/devtools/build/lib/vfs",
18+
"//third_party:auto_value",
1819
"//third_party:error_prone_annotations",
20+
"//third_party:gson",
1921
"//third_party:guava",
2022
],
2123
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.authandtls.credentialhelper;
16+
17+
import com.google.auto.value.AutoValue;
18+
import com.google.common.base.Preconditions;
19+
import com.google.errorprone.annotations.Immutable;
20+
import com.google.gson.JsonSyntaxException;
21+
import com.google.gson.TypeAdapter;
22+
import com.google.gson.annotations.JsonAdapter;
23+
import com.google.gson.stream.JsonReader;
24+
import com.google.gson.stream.JsonToken;
25+
import com.google.gson.stream.JsonWriter;
26+
import java.io.IOException;
27+
import java.net.URI;
28+
import java.util.Locale;
29+
30+
/**
31+
* Request for the {@code get} command of the <a
32+
* href="https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md#proposal">Credential
33+
* Helper Protocol</a>.
34+
*/
35+
@AutoValue
36+
@AutoValue.CopyAnnotations
37+
@Immutable
38+
@JsonAdapter(GetCredentialsRequest.GsonTypeAdapter.class)
39+
public abstract class GetCredentialsRequest {
40+
/** Returns the {@link URI} this request is for. */
41+
public abstract URI getUri();
42+
43+
/** Returns a new builder for {@link GetCredentialsRequest}. */
44+
public static Builder newBuilder() {
45+
return new AutoValue_GetCredentialsRequest.Builder();
46+
}
47+
48+
/** Builder for {@link GetCredentialsRequest}. */
49+
@AutoValue.Builder
50+
public abstract static class Builder {
51+
/** Sets the {@link URI} this request is for. */
52+
public abstract Builder setUri(URI uri);
53+
54+
/** Returns the newly constructed {@link GetCredentialsRequest}. */
55+
public abstract GetCredentialsRequest build();
56+
}
57+
58+
/** GSON adapter for GetCredentialsRequest. */
59+
public static final class GsonTypeAdapter extends TypeAdapter<GetCredentialsRequest> {
60+
@Override
61+
public void write(JsonWriter writer, GetCredentialsRequest value) throws IOException {
62+
Preconditions.checkNotNull(writer);
63+
Preconditions.checkNotNull(value);
64+
65+
writer.beginObject();
66+
writer.name("uri").value(value.getUri().toString());
67+
writer.endObject();
68+
}
69+
70+
@Override
71+
public GetCredentialsRequest read(JsonReader reader) throws IOException {
72+
Preconditions.checkNotNull(reader);
73+
74+
Builder request = newBuilder();
75+
76+
if (reader.peek() != JsonToken.BEGIN_OBJECT) {
77+
throw new JsonSyntaxException(
78+
String.format(Locale.US, "Expected object, got %s", reader.peek()));
79+
}
80+
reader.beginObject();
81+
while (reader.hasNext()) {
82+
String name = reader.nextName();
83+
switch (name) {
84+
case "uri":
85+
if (reader.peek() != JsonToken.STRING) {
86+
throw new JsonSyntaxException(
87+
String.format(
88+
Locale.US, "Expected value of 'url' to be a string, got %s", reader.peek()));
89+
}
90+
request.setUri(URI.create(reader.nextString()));
91+
break;
92+
93+
default:
94+
// We intentionally ignore unknown keys to achieve forward compatibility with requests
95+
// coming from newer tools.
96+
reader.skipValue();
97+
}
98+
}
99+
reader.endObject();
100+
return request.build();
101+
}
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.authandtls.credentialhelper;
16+
17+
import com.google.auto.value.AutoValue;
18+
import com.google.common.base.Preconditions;
19+
import com.google.common.collect.ImmutableList;
20+
import com.google.common.collect.ImmutableMap;
21+
import com.google.errorprone.annotations.Immutable;
22+
import com.google.gson.JsonSyntaxException;
23+
import com.google.gson.TypeAdapter;
24+
import com.google.gson.annotations.JsonAdapter;
25+
import com.google.gson.stream.JsonReader;
26+
import com.google.gson.stream.JsonToken;
27+
import com.google.gson.stream.JsonWriter;
28+
import java.io.IOException;
29+
import java.util.Locale;
30+
import java.util.Map;
31+
32+
/**
33+
* Response from the {@code get} command of the <a
34+
* href="https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md#proposal">Credential
35+
* Helper Protocol</a>.
36+
*/
37+
@AutoValue
38+
@AutoValue.CopyAnnotations
39+
@Immutable
40+
@JsonAdapter(GetCredentialsResponse.GsonTypeAdapter.class)
41+
public abstract class GetCredentialsResponse {
42+
/** Returns the headers to attach to the request. */
43+
public abstract ImmutableMap<String, ImmutableList<String>> getHeaders();
44+
45+
/** Returns a new builder for {@link GetCredentialsRequest}. */
46+
public static Builder newBuilder() {
47+
return new AutoValue_GetCredentialsResponse.Builder();
48+
}
49+
50+
/** Builder for {@link GetCredentialsResponse}. */
51+
@AutoValue.Builder
52+
public abstract static class Builder {
53+
protected abstract ImmutableMap.Builder<String, ImmutableList<String>> headersBuilder();
54+
55+
/** Returns the newly constructed {@link GetCredentialsResponse}. */
56+
public abstract GetCredentialsResponse build();
57+
}
58+
59+
/** GSON adapter for GetCredentialsResponse. */
60+
public static final class GsonTypeAdapter extends TypeAdapter<GetCredentialsResponse> {
61+
@Override
62+
public void write(JsonWriter writer, GetCredentialsResponse response) throws IOException {
63+
Preconditions.checkNotNull(writer);
64+
Preconditions.checkNotNull(response);
65+
66+
writer.beginObject();
67+
68+
ImmutableMap<String, ImmutableList<String>> headers = response.getHeaders();
69+
if (!headers.isEmpty()) {
70+
writer.name("headers");
71+
writer.beginObject();
72+
for (Map.Entry<String, ImmutableList<String>> entry : headers.entrySet()) {
73+
writer.name(entry.getKey());
74+
75+
writer.beginArray();
76+
for (String value : entry.getValue()) {
77+
writer.value(value);
78+
}
79+
writer.endArray();
80+
}
81+
writer.endObject();
82+
}
83+
writer.endObject();
84+
}
85+
86+
@Override
87+
public GetCredentialsResponse read(JsonReader reader) throws IOException {
88+
Preconditions.checkNotNull(reader);
89+
90+
GetCredentialsResponse.Builder response = newBuilder();
91+
92+
if (reader.peek() != JsonToken.BEGIN_OBJECT) {
93+
throw new JsonSyntaxException(
94+
String.format(Locale.US, "Expected object, got %s", reader.peek()));
95+
}
96+
reader.beginObject();
97+
98+
while (reader.hasNext()) {
99+
String name = reader.nextName();
100+
switch (name) {
101+
case "headers":
102+
if (reader.peek() != JsonToken.BEGIN_OBJECT) {
103+
throw new JsonSyntaxException(
104+
String.format(
105+
Locale.US,
106+
"Expected value of 'headers' to be an object, got %s",
107+
reader.peek()));
108+
}
109+
reader.beginObject();
110+
111+
while (reader.hasNext()) {
112+
String headerName = reader.nextName();
113+
ImmutableList.Builder<String> headerValues = ImmutableList.builder();
114+
115+
if (reader.peek() != JsonToken.BEGIN_ARRAY) {
116+
throw new JsonSyntaxException(
117+
String.format(
118+
Locale.US,
119+
"Expected value of '%s' header to be an array of strings, got %s",
120+
headerName,
121+
reader.peek()));
122+
}
123+
reader.beginArray();
124+
for (int i = 0; reader.hasNext(); i++) {
125+
if (reader.peek() != JsonToken.STRING) {
126+
throw new JsonSyntaxException(
127+
String.format(
128+
Locale.US,
129+
"Expected value %s of '%s' header to be a string, got %s",
130+
i,
131+
headerName,
132+
reader.peek()));
133+
}
134+
headerValues.add(reader.nextString());
135+
}
136+
reader.endArray();
137+
138+
response.headersBuilder().put(headerName, headerValues.build());
139+
}
140+
141+
reader.endObject();
142+
break;
143+
144+
default:
145+
// We intentionally ignore unknown keys to achieve forward compatibility with responses
146+
// coming from newer tools.
147+
reader.skipValue();
148+
}
149+
}
150+
reader.endObject();
151+
return response.build();
152+
}
153+
}
154+
}

src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ java_test(
2626
"//src/main/java/com/google/devtools/build/lib/vfs",
2727
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
2828
"//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs",
29+
"//third_party:gson",
2930
"//third_party:guava",
3031
"//third_party:junit4",
3132
"//third_party:truth",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.authandtls.credentialhelper;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
import static org.junit.Assert.assertThrows;
19+
20+
import com.google.gson.Gson;
21+
import com.google.gson.JsonSyntaxException;
22+
import java.net.URI;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
import org.junit.runners.JUnit4;
26+
27+
/** Tests for {@link GetCredentialsRequest}. */
28+
@RunWith(JUnit4.class)
29+
public class GetCredentialsRequestTest {
30+
private static final Gson GSON = new Gson();
31+
32+
@Test
33+
public void parseValid() {
34+
assertThat(
35+
GSON.fromJson("{\"uri\": \"http://example.com\"}", GetCredentialsRequest.class)
36+
.getUri())
37+
.isEqualTo(URI.create("http://example.com"));
38+
assertThat(
39+
GSON.fromJson("{\"uri\": \"https://example.com\"}", GetCredentialsRequest.class)
40+
.getUri())
41+
.isEqualTo(URI.create("https://example.com"));
42+
assertThat(
43+
GSON.fromJson("{\"uri\": \"grpc://example.com\"}", GetCredentialsRequest.class)
44+
.getUri())
45+
.isEqualTo(URI.create("grpc://example.com"));
46+
assertThat(
47+
GSON.fromJson("{\"uri\": \"grpcs://example.com\"}", GetCredentialsRequest.class)
48+
.getUri())
49+
.isEqualTo(URI.create("grpcs://example.com"));
50+
51+
assertThat(
52+
GSON.fromJson("{\"uri\": \"uri-without-protocol\"}", GetCredentialsRequest.class)
53+
.getUri())
54+
.isEqualTo(URI.create("uri-without-protocol"));
55+
}
56+
57+
@Test
58+
public void parseMissingUri() {
59+
assertThrows(JsonSyntaxException.class, () -> GSON.fromJson("{}", GetCredentialsRequest.class));
60+
assertThrows(
61+
JsonSyntaxException.class,
62+
() -> GSON.fromJson("{\"foo\": 1}", GetCredentialsRequest.class));
63+
assertThrows(
64+
JsonSyntaxException.class,
65+
() -> GSON.fromJson("{\"foo\": 1, \"bar\": 2}", GetCredentialsRequest.class));
66+
}
67+
68+
@Test
69+
public void parseNonStringUri() {
70+
assertThrows(JsonSyntaxException.class, () -> GSON.fromJson("[]", GetCredentialsRequest.class));
71+
assertThrows(
72+
JsonSyntaxException.class, () -> GSON.fromJson("\"foo\"", GetCredentialsRequest.class));
73+
assertThrows(JsonSyntaxException.class, () -> GSON.fromJson("1", GetCredentialsRequest.class));
74+
assertThrows(
75+
JsonSyntaxException.class,
76+
() -> GSON.fromJson("{\"uri\": 1}", GetCredentialsRequest.class));
77+
assertThrows(
78+
JsonSyntaxException.class,
79+
() -> GSON.fromJson("{\"uri\": {}}", GetCredentialsRequest.class));
80+
assertThrows(
81+
JsonSyntaxException.class,
82+
() -> GSON.fromJson("{\"uri\": []}", GetCredentialsRequest.class));
83+
assertThrows(
84+
JsonSyntaxException.class,
85+
() -> GSON.fromJson("{\"uri\": [\"https://example.com\"]}", GetCredentialsRequest.class));
86+
assertThrows(
87+
JsonSyntaxException.class,
88+
() -> GSON.fromJson("{\"uri\": null}", GetCredentialsRequest.class));
89+
}
90+
91+
@Test
92+
public void parseWithExtraFields() {
93+
assertThat(
94+
GSON.fromJson(
95+
"{\"uri\": \"http://example.com\", \"foo\": 1}", GetCredentialsRequest.class)
96+
.getUri())
97+
.isEqualTo(URI.create("http://example.com"));
98+
assertThat(
99+
GSON.fromJson(
100+
"{\"foo\": 1, \"uri\": \"http://example.com\"}", GetCredentialsRequest.class)
101+
.getUri())
102+
.isEqualTo(URI.create("http://example.com"));
103+
assertThat(
104+
GSON.fromJson(
105+
"{\"uri\": \"http://example.com\", \"foo\": 1, \"bar\": {}}",
106+
GetCredentialsRequest.class)
107+
.getUri())
108+
.isEqualTo(URI.create("http://example.com"));
109+
assertThat(
110+
GSON.fromJson(
111+
"{\"foo\": 1, \"uri\": \"http://example.com\", \"bar\": []}",
112+
GetCredentialsRequest.class)
113+
.getUri())
114+
.isEqualTo(URI.create("http://example.com"));
115+
}
116+
}

0 commit comments

Comments
 (0)