File tree

3 files changed

+257
-1
lines changed

3 files changed

+257
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ __pycache__
2222

2323
.flattened-pom.xml
2424

25+
# Sample input files
26+
google-analytics-data/oauth2.keys.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/*
2+
* Copyright 2022 Google LLC
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 com.example.analytics;
18+
19+
import com.google.analytics.data.v1beta.BetaAnalyticsDataClient;
20+
import com.google.analytics.data.v1beta.BetaAnalyticsDataSettings;
21+
import com.google.analytics.data.v1beta.DateRange;
22+
import com.google.analytics.data.v1beta.Dimension;
23+
import com.google.analytics.data.v1beta.Metric;
24+
import com.google.analytics.data.v1beta.Row;
25+
import com.google.analytics.data.v1beta.RunReportRequest;
26+
import com.google.analytics.data.v1beta.RunReportResponse;
27+
import com.google.api.client.http.GenericUrl;
28+
import com.google.api.client.http.HttpStatusCodes;
29+
import com.google.api.client.util.Key;
30+
import com.google.api.gax.core.FixedCredentialsProvider;
31+
import com.google.auth.oauth2.ClientId;
32+
import com.google.auth.oauth2.UserAuthorizer;
33+
import com.google.auth.oauth2.UserCredentials;
34+
import com.google.common.base.MoreObjects;
35+
import com.google.common.base.Strings;
36+
import com.google.common.collect.ImmutableList;
37+
import java.io.BufferedReader;
38+
import java.io.FileInputStream;
39+
import java.io.IOException;
40+
import java.io.InputStreamReader;
41+
import java.io.OutputStreamWriter;
42+
import java.io.Writer;
43+
import java.math.BigInteger;
44+
import java.net.ServerSocket;
45+
import java.net.Socket;
46+
import java.net.URI;
47+
import java.nio.charset.StandardCharsets;
48+
import java.security.SecureRandom;
49+
import java.util.regex.Matcher;
50+
import java.util.regex.Pattern;
51+
52+
/**
53+
* Google Analytics Data API sample quickstart application.
54+
*
55+
* <p>This application demonstrates the usage of the Analytics Data API using service account
56+
* credentials.
57+
*
58+
* <p>Before you start the application, please review the comments starting with "TODO(developer)"
59+
* and update the code to use correct values.
60+
*
61+
* <p>To run this sample using Maven:
62+
*
63+
* <pre>
64+
* cd google-analytics-data
65+
* mvn compile
66+
* mvn exec:java -Dexec.mainClass="com.example.analytics.QuickstartOAuth2Sample"
67+
* </pre>
68+
*/
69+
// [START analyticsdata_quickstart_oauth2]
70+
public class QuickstartOAuth2Sample {
71+
// Scopes for the generated OAuth2 credentials. The list here only contains the Google Ads API
72+
// scope, but you can add multiple scopes if you want to use the credentials for other Google
73+
// APIs.
74+
private static final ImmutableList<String> SCOPES =
75+
ImmutableList.<String>builder()
76+
.add("https://www.googleapis.com/auth/analytics.readonly")
77+
.build();
78+
private static final String OAUTH2_CALLBACK_BASE_URI = "http://127.0.0.1";
79+
80+
public static void main(String... args) throws Exception {
81+
/**
82+
* TODO(developer): Replace this variable with your Google Analytics 4 property ID before
83+
* running the sample.
84+
*/
85+
String propertyId = "YOUR-GA4-PROPERTY-ID";
86+
sampleGetCredentialsAndRunReport(propertyId);
87+
}
88+
89+
// This is an example snippet that calls the Google Analytics Data API and runs
90+
// a simple report
91+
// on the provided GA4 property id.
92+
static void sampleGetCredentialsAndRunReport(String propertyId) throws Exception {
93+
// Extracts the OAuth client information from the provided file.
94+
ClientId parsedClient = ClientId.fromStream(new FileInputStream("./oauth2.keys.json"));
95+
// Creates an anti-forgery state token as described here:
96+
// https://developers.google.com/identity/protocols/OpenIDConnect#createxsrftoken
97+
String state = new BigInteger(130, new SecureRandom()).toString(32);
98+
99+
// Creates an HTTP server that will listen for the OAuth2 callback request.
100+
URI baseUri;
101+
UserAuthorizer userAuthorizer;
102+
AuthorizationResponse authorizationResponse = null;
103+
try (SimpleCallbackServer simpleCallbackServer = new SimpleCallbackServer()) {
104+
userAuthorizer =
105+
UserAuthorizer.newBuilder()
106+
.setClientId(parsedClient)
107+
.setScopes(SCOPES)
108+
// Provides an empty callback URI so that no additional suffix is added to the
109+
// redirect. By default, UserAuthorizer will use "/oauth2callback" if this is
110+
// either not set or set to null.
111+
.setCallbackUri(URI.create(""))
112+
.build();
113+
baseUri = URI.create(OAUTH2_CALLBACK_BASE_URI + ":" + simpleCallbackServer.getLocalPort());
114+
System.out.printf(
115+
"Paste this url in your browser:%n%s%n",
116+
userAuthorizer.getAuthorizationUrl(null, state, baseUri));
117+
118+
// Waits for the authorization code.
119+
simpleCallbackServer.accept();
120+
authorizationResponse = simpleCallbackServer.authorizationResponse;
121+
}
122+
123+
if (authorizationResponse == null || authorizationResponse.code == null) {
124+
throw new NullPointerException(
125+
"OAuth2 callback did not contain an authorization code: " + authorizationResponse);
126+
}
127+
128+
// Confirms that the state in the response matches the state token used to
129+
// generate the
130+
// authorization URL.
131+
if (!state.equals(authorizationResponse.state)) {
132+
throw new IllegalStateException("State does not match expected state");
133+
}
134+
135+
// Exchanges the authorization code for credentials and print the refresh token.
136+
UserCredentials userCredentials =
137+
userAuthorizer.getCredentialsFromCode(authorizationResponse.code, baseUri);
138+
System.out.printf("Successfully obtained user credentials for scope(s): %s%n", SCOPES);
139+
140+
// Constructs a BetaAnalyticsDataClient using the UserCredentials obtained.
141+
try (BetaAnalyticsDataClient analyticsData =
142+
BetaAnalyticsDataClient.create(
143+
BetaAnalyticsDataSettings.newBuilder()
144+
.setCredentialsProvider(FixedCredentialsProvider.create(userCredentials))
145+
.build())) {
146+
RunReportRequest request =
147+
RunReportRequest.newBuilder()
148+
.setProperty("properties/" + propertyId)
149+
.addDimensions(Dimension.newBuilder().setName("city"))
150+
.addMetrics(Metric.newBuilder().setName("activeUsers"))
151+
.addDateRanges(DateRange.newBuilder().setStartDate("2020-03-31").setEndDate("today"))
152+
.build();
153+
154+
// Make the request.
155+
RunReportResponse response = analyticsData.runReport(request);
156+
157+
System.out.println("Report result:");
158+
// Iterate through every row of the API response.
159+
for (Row row : response.getRowsList()) {
160+
System.out.printf(
161+
"%s, %s%n", row.getDimensionValues(0).getValue(), row.getMetricValues(0).getValue());
162+
}
163+
}
164+
}
165+
166+
/** Basic server that listens for the OAuth2 callback. */
167+
private static class SimpleCallbackServer extends ServerSocket {
168+
169+
private AuthorizationResponse authorizationResponse;
170+
171+
SimpleCallbackServer() throws IOException {
172+
// Passes a port # of zero so that a port will be automatically allocated.
173+
super(0);
174+
}
175+
176+
/**
177+
* Blocks until a connection is made to this server. After this method completes, the
178+
* authorizationResponse of this server will be set, provided the request line is in the
179+
* expected format.
180+
*/
181+
@Override
182+
public Socket accept() throws IOException {
183+
Socket socket = super.accept();
184+
185+
try (BufferedReader in =
186+
new BufferedReader(
187+
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
188+
String callbackRequest = in.readLine();
189+
// Uses a regular expression to extract the request line from the first line of
190+
// the
191+
// callback request, e.g.:
192+
// GET /?code=AUTH_CODE&state=XYZ&scope=https://www.googleapis.com/auth/adwords
193+
// HTTP/1.1
194+
Pattern pattern = Pattern.compile("GET +([^ ]+)");
195+
Matcher matcher = pattern.matcher(Strings.nullToEmpty(callbackRequest));
196+
if (matcher.find()) {
197+
String relativeUrl = matcher.group(1);
198+
authorizationResponse = new AuthorizationResponse(OAUTH2_CALLBACK_BASE_URI + relativeUrl);
199+
}
200+
try (Writer outputWriter = new OutputStreamWriter(socket.getOutputStream())) {
201+
outputWriter.append("HTTP/1.1 ");
202+
outputWriter.append(Integer.toString(HttpStatusCodes.STATUS_CODE_OK));
203+
outputWriter.append(" OK\n");
204+
outputWriter.append("Content-Type: text/html\n\n");
205+
206+
outputWriter.append("<b>");
207+
if (authorizationResponse.code != null) {
208+
outputWriter.append("Authorization code was successfully retrieved.");
209+
} else {
210+
outputWriter.append("Failed to retrieve authorization code.");
211+
}
212+
outputWriter.append("</b>");
213+
outputWriter.append("<p>Please check the console output from <code>");
214+
outputWriter.append(QuickstartOAuth2Sample.class.getSimpleName());
215+
outputWriter.append("</code> for further instructions.");
216+
}
217+
}
218+
return socket;
219+
}
220+
}
221+
222+
/** Response object with attributes corresponding to OAuth2 callback parameters. */
223+
static class AuthorizationResponse extends GenericUrl {
224+
225+
/** The authorization code to exchange for an access token and (optionally) a refresh token. */
226+
@Key String code;
227+
228+
/** Error from the request or from the processing of the request. */
229+
@Key String error;
230+
231+
/** State parameter from the callback request. */
232+
@Key String state;
233+
234+
/**
235+
* Constructs a new instance based on an absolute URL. All fields annotated with the {@link Key}
236+
* annotation will be set if they are present in the URL.
237+
*
238+
* @param encodedUrl absolute URL with query parameters.
239+
*/
240+
public AuthorizationResponse(String encodedUrl) {
241+
super(encodedUrl);
242+
}
243+
244+
@Override
245+
public String toString() {
246+
return MoreObjects.toStringHelper(getClass())
247+
.add("code", code)
248+
.add("error", error)
249+
.add("state", state)
250+
.toString();
251+
}
252+
}
253+
}
254+
// [END analyticsdata_quickstart_oauth2]
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public static void main(String... args) throws Exception {
4545
* TODO(developer): Replace this variable with your Google Analytics 4 property ID before
4646
* running the sample.
4747
*/
48-
String propertyId = "355158046";
48+
String propertyId = "YOUR-GA4-PROPERTY-ID";
4949
sampleRunReport(propertyId);
5050
}
5151

0 commit comments

Comments
 (0)