|
| 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] |
0 commit comments