إليك نموذج يمكنك استخدامه للحصول على إذن:
Java
// Copyright 2024 Google LLC
//
// 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
//
// https://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 shopping.merchant.samples.utils;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.util.Key;
import com.google.auth.oauth2.ClientId;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.UserAuthorizer;
import com.google.auth.oauth2.UserCredentials;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Class that contains all the authentication logic, both for service accounts and to create an
* OAuth 2 refresh token for the Merchant API.
*
* <p>IMPORTANT FOR OAUTH: For web app clients types, you must add {@code http://127.0.0.1} to the
* "Authorized redirect URIs" list in your Google Cloud Console project before running this example.
* Desktop app client types do not require the local redirect to be explicitly configured in the
* console.
*
* <p>This example will start a basic server that listens for requests at {@code
* http://127.0.0.1:PORT}, where {@code PORT} is dynamically assigned.
*/
public class Authenticator {
// OAUTH2_CALLBACK_BASE_URI set to localhost by default
private static final String OAUTH2_CALLBACK_BASE_URI = "http://127.0.0.1";
// Scopes for the generated OAuth2 credentials. The list here only contains the Merchant API
// scope, but you can add multiple scopes if you want to use the credentials for other Google
// APIs.
private static final ImmutableList<String> SCOPES =
ImmutableList.<String>builder().add("https://www.googleapis.com/auth/content").build();
public GoogleCredentials authenticate() throws IOException {
Config config = Config.load();
if (config.getPath() == null) {
throw new IllegalArgumentException(
"Must update Config.java to set a configuration directory.");
}
File serviceAccountFile = new File(config.getPath(), "service-account.json");
System.out.printf("Checking for service account file at: %s%n", serviceAccountFile);
if (serviceAccountFile.exists()) {
System.out.println("Attempting to load service account credentials");
try (InputStream inputStream = new FileInputStream(serviceAccountFile)) {
GoogleCredentials credential = GoogleCredentials.fromStream(inputStream);
System.out.println("Successfully loaded service account credentials");
return credential;
}
}
System.out.println("No service account file found.");
// Non-service account OAuth flow below
// First see if a refresh token exists, and if so, use it
File tokenFile = new File(config.getPath(), "token.json");
System.out.printf("Checking for user credentials file at: %s%n", tokenFile);
if (tokenFile.exists()) {
System.out.println("Loading OAuth2 refresh token.");
UserCredentials userCredentials = UserCredentials.fromStream(new FileInputStream(tokenFile));
System.out.println("Successfully loaded OAuth2 refresh token");
return userCredentials;
}
// If the refresh token does not exist, attempt to use client
// credentials to get a refresh token
File clientSecretsFile = new File(config.getPath(), "client-secrets.json");
if (!clientSecretsFile.exists()) {
throw new IOException(
"No authentication credentials found. Checked the paths "
+ serviceAccountFile.getCanonicalPath()
+ " and "
+ clientSecretsFile.getCanonicalPath()
+ ". Please read the accompanying README.");
}
System.out.println("Loading OAuth2 client credentials.");
try (InputStream inputStream = new FileInputStream(clientSecretsFile)) {
ClientId parsedClient = ClientId.fromStream(inputStream);
String clientId = parsedClient.getClientId();
String clientSecret = parsedClient.getClientSecret();
// Creates an anti-forgery state token as described here:
// https://developers.google.com/identity/protocols/OpenIDConnect#createxsrftoken
String state = new BigInteger(130, new SecureRandom()).toString(32);
// Creates an HTTP server that will listen for the OAuth2 callback request.
URI baseUri;
UserAuthorizer userAuthorizer;
AuthorizationResponse authorizationResponse = null;
try (SimpleCallbackServer simpleCallbackServer = new SimpleCallbackServer()) {
userAuthorizer =
UserAuthorizer.newBuilder()
.setClientId(ClientId.of(clientId, clientSecret))
.setScopes(SCOPES)
// Provides an empty callback URI so that no additional suffix is added to the
// redirect. By default, UserAuthorizer will use "/oauth2callback" if this is
// either
// not set or set to null.
.setCallbackUri(URI.create(""))
.build();
baseUri =
URI.create(OAUTH2_CALLBACK_BASE_URI + ":" + simpleCallbackServer.getLocalPort());
System.out.printf(
"Paste this url in your browser:%n%s%n",
userAuthorizer.getAuthorizationUrl("", state, baseUri));
// Waits for the authorization code.
simpleCallbackServer.accept();
authorizationResponse = simpleCallbackServer.authorizationResponse;
}
if (authorizationResponse == null || authorizationResponse.code == null) {
throw new NullPointerException(
"OAuth2 callback did not contain an authorization code: " + authorizationResponse);
}
// Confirms that the state in the response matches the state token used to generate the
// authorization URL.
if (!state.equals(authorizationResponse.state)) {
throw new IllegalStateException("State does not match expected state");
}
// Exchanges the authorization code for credentials and print the refresh token.
UserCredentials userCredentials =
userAuthorizer.getCredentialsFromCode(authorizationResponse.code, baseUri);
System.out.printf("Your new refresh token is: %s%n", userCredentials.getRefreshToken());
// Save the refresh token to be used for the future
userCredentials.save(new File(config.getPath(), "token.json").getPath());
return userCredentials;
} catch (IOException e) {
throw new IOException(
"Could not retrieve OAuth2 client credentials from the file "
+ clientSecretsFile.getCanonicalPath());
}
}
/** Basic server that listens for the OAuth2 callback. */
private static class SimpleCallbackServer extends ServerSocket {
private AuthorizationResponse authorizationResponse;
SimpleCallbackServer() throws IOException {
// Passes a port # of zero so that a port will be automatically allocated.
super(0);
}
/**
* Blocks until a connection is made to this server. After this method completes, the
* authorizationResponse of this server will be set, provided the request line is in the
* expected format.
*/
@Override
public Socket accept() throws IOException {
Socket socket = super.accept();
try (BufferedReader in =
new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
String callbackRequest = in.readLine();
// Uses a regular expression to extract the request line from the first line of the
// callback request, e.g.:
// GET /?code=AUTH_CODE&state=XYZ&scope=https://www.googleapis.com/auth/adwords HTTP/1.1
Pattern pattern = Pattern.compile("GET +([^ ]+)");
Matcher matcher = pattern.matcher(Strings.nullToEmpty(callbackRequest));
if (matcher.find()) {
String relativeUrl = matcher.group(1);
authorizationResponse = new AuthorizationResponse(OAUTH2_CALLBACK_BASE_URI + relativeUrl);
}
try (Writer outputWriter = new OutputStreamWriter(socket.getOutputStream())) {
outputWriter.append("HTTP/1.1 ");
outputWriter.append(Integer.toString(HttpStatusCodes.STATUS_CODE_OK));
outputWriter.append(" OK\n");
outputWriter.append("Content-Type: text/html\n\n");
outputWriter.append("<b>");
if (authorizationResponse.code != null) {
outputWriter.append("Authorization code was successfully retrieved.");
} else {
outputWriter.append("Failed to retrieve authorization code.");
}
outputWriter.append("</b>");
outputWriter.append("<p>Please check the console output from <code>");
outputWriter.append(Authenticator.class.getSimpleName());
outputWriter.append("</code> for further instructions.");
}
}
return socket;
}
}
/** Response object with attributes corresponding to OAuth2 callback parameters. */
static class AuthorizationResponse extends GenericUrl {
/** The authorization code to exchange for an access token and (optionally) a refresh token. */
@Key String code;
/** Error from the request or from the processing of the request. */
@Key String error;
/** State parameter from the callback request. */
@Key String state;
/**
* Constructs a new instance based on an absolute URL. All fields annotated with the {@link Key}
* annotation will be set if they are present in the URL.
*
* @param encodedUrl absolute URL with query parameters.
*/
public AuthorizationResponse(String encodedUrl) {
super(encodedUrl);
}
// @Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("code", code)
.add("error", error)
.add("state", state)
.toString();
}
}
}
PHP
<?php
/**
* Copyright 2023 Google LLC
*
* 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
*
* https://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.
*/
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/Config.php';
use Google\Auth\CredentialsLoader;
use Google\Auth\OAuth2;
use Google\Auth\Credentials\UserRefreshCredentials;
use Psr\Http\Message\ServerRequestInterface;
use React\EventLoop\Loop;
use React\Http\HttpServer;
use React\Http\Message\Response;
use React\Socket\SocketServer;
/*
* A class that provides authentication credentials to use the merchant API.
*/
class Authentication
{
private const SCOPE = 'https://www.googleapis.com/auth/content';
private const AUTHORIZATION_URI = 'https://accounts.google.com/o/oauth2/v2/auth';
private const OAUTH2_CALLBACK_IP_ADDRESS = '127.0.0.1';
/**
* This method is called on each sample request, using either the service
* account or stored token.json file to get valid authentication
* credentials.
*/
public static function useServiceAccountOrTokenFile()
{
$config = Config::generateConfig();
print "Attempting to load service account information." . PHP_EOL;
if (file_exists($config['serviceAccountFile'])) {
print 'Service account file exists, will use service account '
. 'to authenticate.'
. PHP_EOL;
return $config['serviceAccountFile'];
} else {
print 'Service account file does not exist, attempting to load '
. 'token file.'
. PHP_EOL;
// Check if the token file exists, and if so, return the contents of the file. Ensure
// you are running this code from the root directory of the PHP samples.
if (file_exists($config['tokenFile'])) {
// Read in the object of `refresh_token`, `client_secret` and
// `client_id` and cast it to an array.
$tokenJSON = (array) json_decode(
file_get_contents($config['tokenFile'])
);
// Create OAuth credentials to be used in the merchant api
// client requests.
$credentials = new UserRefreshCredentials(
scope: null,
jsonKey: $tokenJSON
);
print 'Token file exists, will use token file to authenticate.'
. PHP_EOL;
return $credentials;
} else {
print 'Token file does not exist, attempting to see if client '
. 'secrets file exists.'
. PHP_EOL;
if (file_exists($config['clientSecretsFile'])) {
throw new Exception(
'Client secrets file exists, please run the '
. '`GenerateUserCredentials example to use your client '
. 'secrets to get OAuth2 refresh token. Then re-run your '
. 'sample.'
. PHP_EOL
);
} else {
throw new Exception(
'Service account file, token file, and client secrets '
. 'file do not exist. Please follow the instructions in '
. 'the top level ReadMe to create a service account or '
. 'client secrets file.'
. PHP_EOL
);
}
}
}
}
/**
* This function goes through the OAuth Flow with your client secrets
* to generate and store a token.json file to use for authentication.
*/
public static function generateUserCredentials(): void
{
$config = Config::generateConfig();
print 'Token file does not exist, attempting to load client secrets '
. 'file.'
. PHP_EOL;
if (file_exists($config['clientSecretsFile'])) {
print 'Client secrets file exists, will use client secrets to get '
. 'an OAuth2 refresh token.'
. PHP_EOL;
if (!class_exists(HttpServer::class)) {
print 'Please install "react/http" package to be able to run '
. 'this example';
throw new Exception('Please install "react/http" package');
}
// Creates a socket for localhost with random port. Port 0 is used
// to tell the SocketServer to create a server with a random port.
$socket = new SocketServer(self::OAUTH2_CALLBACK_IP_ADDRESS . ':0');
// Ensure you've created a client secrets file in the appropriate
// location by following the instructions in the top level ReadMe.
// Remember that if you are using a web application, you need to add
// the following to its "Authorized redirect URIs": http://127.0.0.1
// in your GCP console (https://console.cloud.google.com/).
// Read the Client Secrets JSON file.
$json = file_get_contents($config['clientSecretsFile']);
// Decode the JSON file.
$json_data = json_decode($json, true);
$redirectUrl = str_replace('tcp:', 'http:', $socket->getAddress());
$oauth2 = new OAuth2(
[
'clientId' => $json_data['web']['client_id'],
'clientSecret' => $json_data['web']['client_secret'],
'authorizationUri' => $json_data['web']['auth_uri'],
'redirectUri' => $redirectUrl,
'tokenCredentialUri' => $json_data['web']['token_uri'],
'scope' => self::SCOPE,
// Create a 'state' token to prevent request forgery. See
// https://developers.google.com/identity/protocols/OpenIDConnect#createxsrftoken
// for details.
'state' => sha1(openssl_random_pseudo_bytes(1024))
]
);
$authToken = null;
$server = new HttpServer(
function (ServerRequestInterface $request) use ($oauth2, &$authToken, $json_data) {
// Stops the server after tokens are retrieved.
if (!is_null($authToken)) {
Loop::stop();
}
// Check if the requested path is the one set as the
// redirect URI. We add '/' here so the parse_url method
// can function correctly, since it cannot detect the URI
// without '/' at the end, which is the case for the value
// of getRedirectUri().
if (
$request->getUri()->getPath()
!== parse_url($oauth2->getRedirectUri() . '/', PHP_URL_PATH)
) {
return new Response(
404,
['Content-Type' => 'text/plain'],
'Page not found'
);
}
// Exit if the state is invalid to prevent request forgery.
$state = $request->getQueryParams()['state'];
if (empty($state) || ($state !== $oauth2->getState())) {
throw new UnexpectedValueException(
"The state is empty or doesn't match the expected one."
. PHP_EOL
);
};
// Set the authorization code and fetch refresh and access
// tokens.
$code = $request->getQueryParams()['code'];
$oauth2->setCode($code);
$authToken = $oauth2->fetchAuthToken();
$refreshToken = $authToken['refresh_token'];
print 'Your refresh token is: ' . $refreshToken . PHP_EOL;
print 'You can now run any example to automatically use '
. 'your new refresh token to generate an access token and '
. 'succesfully authenticate your request.'
. PHP_EOL;
$token_file_credentials = [
'client_id' => $json_data['web']['client_id'],
'client_secret' => $json_data['web']['client_secret'],
'refresh_token' => $refreshToken
];
file_put_contents(
"token.json",
json_encode($token_file_credentials)
);
return new Response(
200,
['Content-Type' => 'text/plain'],
'Your refresh token has been fetched. Check the '
. 'console output for further instructions.'
);
}
);
$server->listen($socket);
printf(
'Log into the Google account you use for Google Ads and visit '
. 'the following URL in your web browser: %1$s%2$s%1$s%1$s',
PHP_EOL,
implode(
[$oauth2->buildFullAuthorizationUri(['access_type' => 'offline']), '&prompt=consent']
)
);
} else {
print 'Client secrets file does not exist. Please follow the '
. 'instructions in the top level ReadMe to create a client secrets '
. 'file.'
. PHP_EOL;
}
}
}
Python
# -*- coding: utf-8 -*-
# Copyright 2024 Google LLC
#
# 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.
"""This example will create credentials to use the Merchant API.
This file authenticates either via a provided service-account.json file, a
stored OAuth2 refresh token, or creates credentials and stores a refresh token
via a provided client-secrets.json file.
This example works with web OAuth client ID types.
https://console.cloud.google.com
IMPORTANT: For web app clients types, you must add "http://127.0.0.1" to the
"Authorized redirect URIs" list in your Google Cloud Console project before
running this example.
"""
import hashlib
import os
import re
import socket
import sys
import urllib.parse
from examples.authentication import configuration
from examples.authentication import token_storage
from google.oauth2 import service_account
# If using Web flow, the redirect URL must match exactly what’s configured in
# GCP for the OAuth client.
from google_auth_oauthlib.flow import Flow
_SCOPE = "https://www.googleapis.com/auth/content"
_SERVER = "127.0.0.1"
_PORT = 8080
_REDIRECT_URI = f"http://{_SERVER}:{_PORT}"
def main():
"""Generates OAuth2 credentials."""
# Gets the configuration object that has the paths on the local machine to
# the `service-account.json`, `token.json`, and `client-secrets.json` files.
config = configuration.Configuration().get_config()
service_account_path = config["service_account_path"]
print("Attempting to use service account credentials from "
f"{service_account_path}.")
if os.path.isfile(service_account_path):
print("Service account credentials found. Attempting to authenticate.")
credentials = service_account.Credentials.from_service_account_file(
service_account_path,
scopes=[_SCOPE])
return credentials
else:
print("Service account credentials not found.")
full_token_path = os.path.join(os.getcwd(), config["token_path"])
print(f"Attempting to use stored token data from {full_token_path}")
if os.path.isfile(config["token_path"]):
print("Token file found.")
print("Attempting to use token file to authenticate")
return get_credentials_from_token(config)
else:
print("Token file not found.")
client_secrets_path = config["client_secrets_path"]
print(f"Attempting to use client secrets from {client_secrets_path}.")
if os.path.isfile(client_secrets_path):
print("Client secrets file found.")
print("Attempting to use client secrets to authenticate")
return get_credentials_from_client_secrets(config)
else:
print("Service account file, token file, and client secrets "
"file do not exist. Please follow the instructions in "
"the top level ReadMe to create a service account or "
"client secrets file.")
exit(1)
def get_credentials_from_token(config):
"""Generates OAuth2 refresh token from stored local token file."""
credentials = token_storage.Storage(config, _SCOPE).get()
return credentials
def get_credentials_from_client_secrets(config):
"""Generates OAuth2 refresh token using the Web application flow.
To retrieve the necessary client_secrets JSON file, first
generate OAuth 2.0 credentials of type Web application in the
Google Cloud Console (https://console.cloud.google.com).
Make sure "http://_SERVER:_PORT" is included the list of
"Authorized redirect URIs" for this client ID."
Starts a basic server and initializes an auth request.
Args:
config: an instance of the Configuration object.
Returns:
Credentials used to authenticate with the Merchant API.
"""
# A list of API scopes to include in the auth request, see:
# https://developers.google.com/identity/protocols/oauth2/scopes
scopes = [_SCOPE]
# A path to where the client secrets JSON file is located
# on the machine running this example.
client_secrets_path = config["client_secrets_path"]
flow = Flow.from_client_secrets_file(client_secrets_path, scopes=scopes)
flow.redirect_uri = _REDIRECT_URI
# Create an anti-forgery state token as described here:
# https://developers.google.com/identity/protocols/OpenIDConnect#createxsrftoken
passthrough_val = hashlib.sha256(os.urandom(1024)).hexdigest()
authorization_url, state = flow.authorization_url(
access_type="offline",
state=passthrough_val,
prompt="consent",
include_granted_scopes="true",
)
print(f"Your state token is: {state}\n")
# Prints the authorization URL so you can paste into your browser. In a
# typical web application you would redirect the user to this URL, and they
# would be redirected back to "redirect_url" provided earlier after
# granting permission.
print("Paste this URL into your browser: ")
print(authorization_url)
print(f"\nWaiting for authorization and callback to: {_REDIRECT_URI}")
# Retrieves an authorization code by opening a socket to receive the
# redirect request and parsing the query parameters set in the URL.
code = urllib.parse.unquote(get_authorization_code(passthrough_val))
# Passes the code back into the OAuth module to get a refresh token.
flow.fetch_token(code=code)
refresh_token = flow.credentials.refresh_token
print(f"\nYour refresh token is: {refresh_token}\n")
# Stores the provided credentials into the appropriate file.
storage = token_storage.Storage(config, scopes)
storage.put(flow.credentials)
return flow.credentials
def get_authorization_code(passthrough_val):
"""Opens a socket to handle a single HTTP request containing auth tokens.
Args:
passthrough_val: an anti-forgery token used to verify the request
received by the socket.
Returns:
a str access token from the Google Auth service.
"""
# Opens a socket at _SERVER:_PORT and listen for a request.
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((_SERVER, _PORT))
sock.listen(1)
connection, address = sock.accept()
print(f"Socket address: {address}")
data = connection.recv(1024)
# Parses the raw request to retrieve the URL query parameters.
params = parse_raw_query_params(data)
try:
if not params.get("code"):
# If no code is present in the query params then there will be an
# error message with more details.
error = params.get("error")
message = f"Failed to retrieve authorization code. Error: {error}"
raise ValueError(message)
elif params.get("state") != passthrough_val:
message = "State token does not match the expected state."
raise ValueError(message)
else:
message = "Authorization code was successfully retrieved."
except ValueError as error:
print(error)
sys.exit(1)
finally:
response = (
"HTTP/1.1 200 OK\n"
"Content-Type: text/html\n\n"
f"<b>{message}</b>"
"<p>Please check the console output.</p>\n"
)
connection.sendall(response.encode())
connection.close()
return params.get("code")
def parse_raw_query_params(data):
"""Parses a raw HTTP request to extract its query params as a dict.
Note that this logic is likely irrelevant if you're building OAuth logic
into a complete web application, where response parsing is handled by a
framework.
Args:
data: raw request data as bytes.
Returns:
a dict of query parameter key value pairs.
"""
# Decodes the request into a utf-8 encoded string.
decoded = data.decode("utf-8")
# Uses a regular expression to extract the URL query parameters string.
params = re.search(r"GET\s\/\?(.*) ", decoded).group(1)
# Splits the parameters to isolate the key/value pairs.
pairs = [pair.split("=") for pair in params.split("&")]
# Converts pairs to a dict to make it easy to access the values.
return {key: val for key, val in pairs}