Skip to content

Commit 049282b

Browse files
authored
Merge pull request #72 from cisco-open/feature/master/add-oauth-support
Add support for OAuth authentication
2 parents 9b7040b + f396317 commit 049282b

File tree

7 files changed

+162
-15
lines changed

7 files changed

+162
-15
lines changed

src/main/java/io/opentelemetry/contrib/generator/telemetry/cli/CLIProcessor.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.opentelemetry.contrib.generator.telemetry.transport.auth.AuthHandler;
2525
import io.opentelemetry.contrib.generator.telemetry.transport.auth.BasicAuthHandler;
2626
import io.opentelemetry.contrib.generator.telemetry.transport.auth.NoAuthHandler;
27+
import io.opentelemetry.contrib.generator.telemetry.transport.auth.OAuthHandler;
2728
import io.opentelemetry.contrib.generator.telemetry.transport.implementations.grpc.GRPCPayloadHandler;
2829
import io.opentelemetry.contrib.generator.telemetry.transport.implementations.rest.RESTPayloadHandler;
2930
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -138,25 +139,36 @@ private static PayloadHandler getPayloadHandler(String targetEnvYAML) {
138139
throw new GeneratorException("Either restURL (for REST endpoint) or gRPCHost & gRPCPort (for gRPC endpoint) " +
139140
"must be provided in environment target YAML");
140141
}
141-
AuthHandler authHandler;
142-
if (targetEnvironmentDetails.getAuthMode() == null) {
143-
throw new GeneratorException("Missing authMode in environment target YAML");
144-
}
145-
if (!(targetEnvironmentDetails.getAuthMode().equalsIgnoreCase("NONE") ||
146-
targetEnvironmentDetails.getAuthMode().equalsIgnoreCase("BASIC"))) {
147-
throw new GeneratorException("Invalid authMode in environment target YAML. Allowed - NONE, BASIC");
142+
String authMode = StringUtils.defaultString(targetEnvironmentDetails.getAuthMode()).toUpperCase();
143+
if (authMode.isBlank() || (!authMode.equals("NONE") && !authMode.equals("BASIC") && !authMode.equals("OAUTH"))) {
144+
log.warn("authMode not provided or invalid value provided in environment target YAML. Valid values are - none/basic/oauth." +
145+
"Will use none authentication for data posting.");
146+
authMode = "NONE";
148147
}
149-
if (targetEnvironmentDetails.getAuthMode().equalsIgnoreCase("NONE")) {
148+
AuthHandler authHandler;
149+
if (authMode.equals("NONE")) {
150150
authHandler = new NoAuthHandler();
151-
} else {
151+
} else if (authMode.equals("BASIC")) {
152152
if (StringUtils.defaultString(targetEnvironmentDetails.getUsername()).isBlank()) {
153-
throw new GeneratorException("Missing username in environment target YAML");
153+
throw new GeneratorException("Select auth mode is Basic but username not provided");
154154
}
155155
if (StringUtils.defaultString(targetEnvironmentDetails.getPassword()).isBlank()) {
156-
throw new GeneratorException("Missing password in environment target YAML");
156+
throw new GeneratorException("Select auth mode is Basic but password not provided");
157157
}
158158
authHandler = new BasicAuthHandler(targetEnvironmentDetails.getUsername(),
159159
targetEnvironmentDetails.getPassword());
160+
} else {
161+
if (StringUtils.defaultString(targetEnvironmentDetails.getTokenURL()).isBlank()) {
162+
throw new GeneratorException("Select auth mode is OAuth but tokenURL not provided");
163+
}
164+
if (StringUtils.defaultString(targetEnvironmentDetails.getClientId()).isBlank()) {
165+
throw new GeneratorException("Select auth mode is Basic but clientId not provided");
166+
}
167+
if (StringUtils.defaultString(targetEnvironmentDetails.getClientSecret()).isBlank()) {
168+
throw new GeneratorException("Select auth mode is Basic but clientSecret not provided");
169+
}
170+
authHandler = new OAuthHandler(targetEnvironmentDetails.getTokenURL(), targetEnvironmentDetails.getClientId(),
171+
targetEnvironmentDetails.getClientSecret(), targetEnvironmentDetails.getScope());
160172
}
161173
if (restURLProvided) {
162174
String restBaseURL = targetEnvironmentDetails.getRestURL().getBaseURL();

src/main/java/io/opentelemetry/contrib/generator/telemetry/cli/dto/TargetEnvironmentDetails.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,8 @@ public class TargetEnvironmentDetails {
2727
private String authMode;
2828
private String username;
2929
private String password;
30+
private String tokenURL;
31+
private String clientId;
32+
private String clientSecret;
33+
private String scope;
3034
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2022 AppDynamics Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opentelemetry.contrib.generator.telemetry.transport.auth;
18+
19+
import com.fasterxml.jackson.core.JsonProcessingException;
20+
import com.fasterxml.jackson.databind.JsonNode;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import io.opentelemetry.contrib.generator.telemetry.transport.implementations.HTTPClient;
23+
import lombok.extern.slf4j.Slf4j;
24+
25+
import javax.ws.rs.core.HttpHeaders;
26+
import java.util.Base64;
27+
import java.util.Optional;
28+
import java.util.concurrent.TimeUnit;
29+
30+
/**
31+
* Class to handle OAuth client credentials workflow which will generate the access token to be used with every request.
32+
* See <a href="https://www.rfc-editor.org/rfc/rfc6749#section-4.4">OAuth2 Client Credentials specification</a>
33+
* In case of a different implementation of the OAuth, please use a custom implementation.
34+
*/
35+
@Slf4j
36+
public class OAuthHandler implements AuthHandler {
37+
38+
private final String GET_TOKEN_URL;
39+
private final String CLIENT_ID;
40+
private final String CLIENT_SECRET;
41+
//Provide null for scope if not required
42+
private final String SCOPE;
43+
private final HTTPClient httpClient;
44+
private String accessToken;
45+
private long expirySeconds;
46+
47+
public OAuthHandler(String GET_TOKEN_URL, String CLIENT_ID, String CLIENT_SECRET, String SCOPE) {
48+
this.GET_TOKEN_URL = GET_TOKEN_URL;
49+
this.CLIENT_ID = CLIENT_ID;
50+
this.CLIENT_SECRET = CLIENT_SECRET;
51+
this.SCOPE = SCOPE;
52+
httpClient = new HTTPClient();
53+
accessToken = "";
54+
expirySeconds = -1;
55+
}
56+
57+
@Override
58+
public String getAuthString() {
59+
if (expirySeconds < TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())) {
60+
setAccessToken();
61+
}
62+
return "Bearer " + accessToken;
63+
}
64+
65+
private void setAccessToken() {
66+
var body = "grant_type=client_credentials";
67+
if (SCOPE != null)
68+
{
69+
body = body + "&scope=" + SCOPE;
70+
}
71+
Optional<String> responseBody = httpClient.postBytes(GET_TOKEN_URL, getHeadersToken(CLIENT_ID, CLIENT_SECRET), body.getBytes());
72+
if (responseBody.isEmpty()) {
73+
log.error("Failed to get fresh token. Post data requests may fail.");
74+
return;
75+
}
76+
extractAndSetToken(responseBody.get());
77+
}
78+
79+
private void extractAndSetToken(String tokenSecretResponse) {
80+
var responseMapper = new ObjectMapper();
81+
boolean failure = false;
82+
JsonNode responseBody = null;
83+
try {
84+
responseBody = responseMapper.readTree(tokenSecretResponse);
85+
} catch (JsonProcessingException e) {
86+
log.error("Failed to parse json out of token secret response: " + tokenSecretResponse);
87+
failure = true;
88+
}
89+
if (responseBody == null || !responseBody.has("access_token")) {
90+
log.error("access_token not found in get access token response body: " + tokenSecretResponse);
91+
failure = true;
92+
} else {
93+
accessToken = responseBody.get("access_token").asText();
94+
}
95+
if (responseBody == null || !responseBody.has("expires_in")) {
96+
log.warn("expires_in field not found in get access token response body: " + tokenSecretResponse);
97+
log.warn("Will use default expiry time of 60 minutes.");
98+
} else {
99+
expirySeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) + responseBody.get("expires_in").asLong();
100+
}
101+
if (failure) {
102+
log.error("Failed to get fresh token. Post data requests may fail.");
103+
}
104+
}
105+
106+
private String[] getHeadersToken(String id, String secret) {
107+
return new String[] {HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8",
108+
HttpHeaders.AUTHORIZATION, "Basic " + getEncodedBasicAuth(id, secret)};
109+
}
110+
111+
private String getEncodedBasicAuth(String id, String secret) {
112+
String authStr = id + ":" + secret;
113+
return Base64.getEncoder().encodeToString(authStr.getBytes());
114+
}
115+
116+
}

src/main/java/io/opentelemetry/contrib/generator/telemetry/transport/implementations/grpc/GRPCPayloadHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public GRPCPayloadHandler(String host, int gRPCPort, AuthHandler authHandler) {
5252
this.HOST = host;
5353
this.gRPCPORT = gRPCPort;
5454
this.authHandler = authHandler;
55-
isAuthEnabled = authHandler instanceof NoAuthHandler;
55+
isAuthEnabled = !(authHandler instanceof NoAuthHandler);
5656
}
5757

5858
@Override

src/main/java/io/opentelemetry/contrib/generator/telemetry/transport/implementations/rest/RESTPayloadHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public RESTPayloadHandler(String endpointURL, AuthHandler authHandler) {
5555
this.ENDPOINT_URL = endpointURL;
5656
httpClient = new HTTPClient();
5757
this.authHandler = authHandler;
58-
isAuthEnabled = authHandler instanceof NoAuthHandler;
58+
isAuthEnabled = !(authHandler instanceof NoAuthHandler);
5959
}
6060

6161
@Override

src/test/java/io/opentelemetry/contrib/generator/telemetry/TestCLIProcessor.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ public void testMetricsTracesBasicAuthGRPC() throws ParseException {
5252
CLIProcessor.main(cliArgs);
5353
}
5454

55+
@Test
56+
public void testAllGeneratorsOAuthGRPC() throws ParseException {
57+
String oauthTargetYAML = Paths.get(cliResourcesPath, "target-oauth.yaml").toString();
58+
String[] cliArgs = new String[] {
59+
"-r", RESOURCES_YAML, "-m", METRICS_YAML, "-l", LOGS_YAML, "-s", TRACES_YAML, "-t", oauthTargetYAML
60+
};
61+
CLIProcessor.main(cliArgs);
62+
}
63+
5564
@Test
5665
public void testWithJSONFormatInputs() throws ParseException {
5766
String cliResourcesPath = Paths.get(System.getProperty("user.dir"), "src", "test", "resources",
@@ -88,7 +97,7 @@ public void testWithoutTargetAuthYAML() throws ParseException {
8897
CLIProcessor.main(cliArgs);
8998
}
9099

91-
@Test(expectedExceptions = GeneratorException.class)
100+
@Test
92101
public void testWithoutAuthMode() throws ParseException {
93102
String noAuthModeYAML = Paths.get(cliResourcesPath, "negative", "no-auth-mode.yaml").toString();
94103
String[] cliArgs = new String[] {
@@ -97,7 +106,7 @@ public void testWithoutAuthMode() throws ParseException {
97106
CLIProcessor.main(cliArgs);
98107
}
99108

100-
@Test(expectedExceptions = GeneratorException.class)
109+
@Test
101110
public void testWithInvalidAuthMode() throws ParseException {
102111
String invalidAuthModeYAML = Paths.get(cliResourcesPath, "negative", "invalid-auth-mode.yaml").toString();
103112
String[] cliArgs = new String[] {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
authMode: "oauth"
2+
clientId: "clientId"
3+
clientSecret: "clientSecret"
4+
grpchost: "localhost"
5+
grpcport: "3387"
6+
tokenURL: "http://localhost:8888/dummy"

0 commit comments

Comments
 (0)