I need some help guys!! I am a self-taught, newbie in encryption, and after reading, testing, and error for more than two weeks on how to solve this, and finding very little crowd knowledge and almost no documentation from Google.
I am trying to read the integrity verdict, that I have managed to get it IntegrityTokenRequest doing
String nonce = Base64.encodeToString("this_is_my_nonce".getBytes(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
IntegrityManager myIntegrityManager = IntegrityManagerFactory
.create(getApplicationContext());
// Request the integrity token by providing a nonce.
Task<IntegrityTokenResponse> myIntegrityTokenResponse = myIntegrityManager
.requestIntegrityToken(IntegrityTokenRequest
.builder()
.setNonce(nonce)
.build());
myIntegrityTokenResponse.addOnSuccessListener(new OnSuccessListener<IntegrityTokenResponse>() {
#Override
public void onSuccess(IntegrityTokenResponse myIntegrityTokenResponse) {
String token = myIntegrityTokenResponse.token();
// so here I have my Integrity token.
// now how do I read it??
}
}
As per the documentation, it's all set up in the Play Console, and created the Google Cloud project accordingly. Now here comes the big hole in the documentation:
a) The JWT has 4 dots that divide the JWT into 5 sections, not in 3 sections as described here https://jwt.io/
b) Developer.Android.com recommends to Decrypt and Verify on Google Servers
I have no idea on how or were to execute this command... :-(
c) if I choose to decrypt and verify the returned token it's more complicated as I don't have my own secure server environment, only my App and the Google Play Console.
d) I found in the Google Clound Platform OAuth 2.0 Client IDs "Android client for com.company.project" JSON file that I have downloaded, but no clue (again) on how to use it in my App for getting the veredict from the Integrity Token.
{"installed":
{"client_id":"123456789012-abcdefghijklmnopqrstuvwxyza0g2ahk.apps.googleusercontent.com",
"project_id":"myproject-360d3",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url":https://www.googleapis.com/oauth2/v1/certs
}
}
I'm sure I am missing a lot, please help
Using a cloud server to decode and verify the token is better.
For example, if you going with Java service then the below code will send the integrity token to the google server hence you can verify the response.
Enable PlayIntegrity API in Google Cloud Platform against the app and download the JSON file and configure in the code.
Similarly, you should enable PlayIntegrity API in Google PlayConsole against the app
Add Google Play Integrity Client Library to your project
Maven Dependency
<project>
<dependencies>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-playintegrity</artifactId>
<version>v1-rev20220211-1.32.1</version>
</dependency>
</dependencies>
Gradle
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.apis:google-api-services-playintegrity:v1-rev20220211-1.32.1'
}
Token decode
DecodeIntegrityTokenRequest requestObj = new DecodeIntegrityTokenRequest();
requestObj.setIntegrityToken(request.getJws());
//Configure downloaded Json file
GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream("<Path of JSON file>\\file.json"));
HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(credentials);
HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
JsonFactory JSON_FACTORY = new JacksonFactory();
GoogleClientRequestInitializer initialiser = new PlayIntegrityRequestInitializer();
Builder playIntegrity = new PlayIntegrity.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer).setApplicationName("testapp")
.setGoogleClientRequestInitializer(initialiser);
PlayIntegrity play = playIntegrity.build();
DecodeIntegrityTokenResponse response = play.v1().decodeIntegrityToken("com.test.android.integritysample", requestObj).execute();
Then the response will be as follows
{
"tokenPayloadExternal": {
"accountDetails": {
"appLicensingVerdict": "LICENSED"
},
"appIntegrity": {
"appRecognitionVerdict": "PLAY_RECOGNIZED",
"certificateSha256Digest": ["pnpa8e8eCArtvmaf49bJE1f5iG5-XLSU6w1U9ZvI96g"],
"packageName": "com.test.android.integritysample",
"versionCode": "4"
},
"deviceIntegrity": {
"deviceRecognitionVerdict": ["MEETS_DEVICE_INTEGRITY"]
},
"requestDetails": {
"nonce": "SafetyNetSample1654058651834",
"requestPackageName": "com.test.android.integritysample",
"timestampMillis": "1654058657132"
}
}
}
Check for License
String licensingVerdict = response.getTokenPayloadExternal().getAccountDetails().getAppLicensingVerdict();
if(!licensingVerdict.equalsIgnoreCase("LICENSED")) {
throw new Exception("Licence is not valid.");
}
Verify App Integrity
public void checkAppIntegrity(DecodeIntegrityTokenResponse response, String appId) throws Exception {
AppIntegrity appIntegrity = response.getTokenPayloadExternal().getAppIntegrity();
if(!appIntegrity.getAppRecognitionVerdict().equalsIgnoreCase("PLAY_RECOGNIZED")) {
throw new Exception("The certificate or package name does not match Google Play records.");
}
if(!appIntegrity.getPackageName().equalsIgnoreCase(appId)) {
throw new Exception("App package name mismatch.");
}
if(appIntegrity.getCertificateSha256Digest()!= null) {
//If the app is deployed in Google PlayStore then Download the App signing key certificate from Google Play Console (If you are using managed signing key).
//otherwise download Upload key certificate and then find checksum of the certificate.
Certificate cert = getCertificate("<Path to Signing certificate>\deployment_cert.der");
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] der = cert.getEncoded();
md.update(der);
byte[] sha256 = md.digest();
//String checksum = Base64.getEncoder().encodeToString(sha256);
String checksum = Base64.getUrlEncoder().encodeToString(sha256);
/** Sometimes checksum value ends with '=' character, you can avoid this character before perform the match **/
checksum = checksum.replaceAll("=","");
if(!appIntegrity.getCertificateSha256Digest().get(0).contains(checksum)) {
throw new Exception("App certificate mismatch.");
}
}
}
public static Certificate getCertificate(String certificatePath)
throws Exception {
CertificateFactory certificateFactory = CertificateFactory
.getInstance("X509");
FileInputStream in = new FileInputStream(certificatePath);
Certificate certificate = certificateFactory
.generateCertificate(in);
in.close();
return certificate;
}
Verify Device integrity
//Check Device Integrity
public void deviceIntegrity(DecodeIntegrityTokenResponse response) {
DeviceIntegrity deviceIntegrity = response.getTokenPayloadExternal().getDeviceIntegrity();
if(!deviceIntegrity.getDeviceRecognitionVerdict().contains("MEETS_DEVICE_INTEGRITY")) {
throw new Exception("Does not meet Device Integrity.");
}
}
Similary you can verify the Nonce and App Package name with previously stored data in server
Thanks a lot #John_S for your answer, I'll mark it as the final answer, anyway I post here all the missing parts for future developers so they can shortcut my almost one month sucked in this issue, as there is no complete documentation nor java examples (at the time of writing this) for the Google PlayIntegrity API.
First, you need to set our project in the Google Cloud, and Google Play as stated by #John_S, but the missing part is that you need to set a Credential as "Service Account" and then "Add Key" as described java.io.IOException: Error reading credentials from stream, 'type' field not specified and this https://developers.google.com/workspace/guides/create-credentials#android; then, you can download the .json file with your Credentials. The .json file described in my question is invalid as it must have a structure like this:
{ "type": "service_account",
"project_id": "your-project",
"private_key_id": "your-key-id",
"private_key": "your-private-key",
"client_email": "your-email#appspot.gserviceaccount.com",
"client_id": "your-client-id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/your-email%40appspot.gserviceaccount.com"
}
Second, once you have your valid .json file downloaded, store it in "src/main/resources/credentials.json" (create the new folder if needed, not into "res" folder), as stated here Where must the client_secrets.json file go in Android Studio project folder tree?
Third, to complete all the missing parts of the build.gradle you must include:
dependencies {
implementation 'com.google.android.play:integrity:1.0.1'
implementation 'com.google.apis:google-api-services-playintegrity:v1-rev20220211-1.32.1'
implementation 'com.google.api-client:google-api-client-jackson2:1.20.0'
implementation 'com.google.auth:google-auth-library-credentials:1.7.0'
implementation 'com.google.auth:google-auth-library-oauth2-http:1.7.0'
}
And import them to your project
import com.google.android.gms.tasks.Task;
import com.google.android.play.core.integrity.IntegrityManager;
import com.google.android.play.core.integrity.IntegrityManagerFactory;
import com.google.android.play.core.integrity.IntegrityTokenRequest;
import com.google.android.play.core.integrity.IntegrityTokenResponse;
import com.google.api.services.playintegrity.v1.PlayIntegrity;
import com.google.api.services.playintegrity.v1.PlayIntegrityRequestInitializer;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.api.services.playintegrity.v1.model.DecodeIntegrityTokenRequest;
import com.google.api.services.playintegrity.v1.model.DecodeIntegrityTokenResponse;
import com.google.api.client.googleapis.services.GoogleClientRequestInitializer;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
Then, the complete code for requesting the "Integrity Token" and decode it will be:
// create the NONCE Base64-encoded, URL-safe, and non-wrapped String
String mynonce = Base64.encodeToString("this_is_my_nonce".getBytes(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
// Create an instance of a manager.
IntegrityManager myIntegrityManager = IntegrityManagerFactory.create(getApplicationContext());
// Request the integrity token by providing a nonce.
Task<IntegrityTokenResponse> myIntegrityTokenResponse = myIntegrityManager
.requestIntegrityToken(IntegrityTokenRequest
.builder()
.setNonce(mynonce)
// .setCloudProjectNumber(cloudProjNumber) // necessary only if sold outside Google Play
.build());
// get the time to check against the decoded integrity token time
timeRequest = Calendar.getInstance().getTimeInMillis();
myIntegrityTokenResponse.addOnSuccessListener(new OnSuccessListener<IntegrityTokenResponse>() {
#Override
public void onSuccess(IntegrityTokenResponse myIntegrityTokenResponse) {
try {
String token = myIntegrityTokenResponse.token();
DecodeIntegrityTokenRequest requestObj = new DecodeIntegrityTokenRequest();
requestObj.setIntegrityToken(token);
//Configure your credentials from the downloaded Json file from the resource
GoogleCredentials credentials = GoogleCredentials.fromStream(Objects.requireNonNull(getClass().getClassLoader()).getResourceAsStream("credentials.json"));
HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(credentials);
HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
JsonFactory JSON_FACTORY = new JacksonFactory();
GoogleClientRequestInitializer initializer = new PlayIntegrityRequestInitializer();
PlayIntegrity.Builder playIntegrity = new PlayIntegrity.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer).setApplicationName("your-project")
.setGoogleClientRequestInitializer(initializer);
PlayIntegrity play = playIntegrity.build();
// the DecodeIntegrityToken must be run on a parallel thread
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
try {
DecodeIntegrityTokenResponse response = play.v1().decodeIntegrityToken("com.project.name", requestObj).execute();
String licensingVerdict = response.getTokenPayloadExternal().getAccountDetails().getAppLicensingVerdict();
if (licensingVerdict.equalsIgnoreCase("LICENSED")) {
// Looks good! LICENSED app
} else {
// LICENSE NOT OK
}
} catch (Exception e) {
// LICENSE error
}
}
});
// execute the parallel thread
thread.start();
} catch (Error | IOException e) {
// LICENSE error
} catch (Exception e) {
// LICENSE error
}
}
});
Hope this helps.
Related
I am trying to run the TextToSpeech code from Google Cloud TextToSpeech Service.
Curently stuck at Authentication part referring link Authenticating as a service account
Below is the Code :
public class TexttoSpeech {
/** Demonstrates using the Text-to-Speech API. */
public static void getAudio() throws Exception {
// Instantiates a client
// Below Line is Point of Error in Code
try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) {
// Set the text input to be synthesized
SynthesisInput input = SynthesisInput.newBuilder().setText("Hello, World!").build();
// Build the voice request, select the language code ("en-US") and the ssml voice
//gender
// ("neutral")
VoiceSelectionParams voice =
VoiceSelectionParams.newBuilder()
.setLanguageCode("en-US")
.setSsmlGender(SsmlVoiceGender.NEUTRAL)
.build();
// Select the type of audio file you want returned
AudioConfig audioConfig =
AudioConfig.newBuilder().setAudioEncoding(AudioEncoding.MP3).build();
// Perform the text-to-speech request on the text input with the selected voice parameters and
// audio file type
SynthesizeSpeechResponse response =
textToSpeechClient.synthesizeSpeech(input, voice, audioConfig);
// Get the audio contents from the response
ByteString audioContents = response.getAudioContent();
byte[] audioArray=audioContents.toByteArray();
String converted= Base64.encodeBase64String(audioArray);
playAudio(converted);
// Write the response to the output file.
try (OutputStream out = new FileOutputStream("output.mp3")) {
out.write(audioContents.toByteArray());
System.out.println("Audio content written to file \"output.mp3\"");
}
}
}
public static void playAudio(String base64EncodedString){
try
{
String url = "data:audio/mp3;base64,"+base64EncodedString;
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(url);
mediaPlayer.prepare();
mediaPlayer.start();
}
catch(Exception ex){
System.out.print(ex.getMessage());
}
}
}
But getting below error on :
java.io.IOException: The Application Default Credentials are not available. They are available
if running in Google Compute Engine. Otherwise, the environment variable
GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials.
See https://developers.google.com/accounts/docs/application-default-credentials for more
information.
Also tried Explicit credentials :
#Throws(IOException::class)
fun authExplicit() {
val projectID = "texttospeech-12345" // dummy id
// val imageUri: Uri =
Uri.fromFile(File("file:\\android_asset\\service_account_file.json"))
// val path=File(imageUri.path).absolutePath
// You can specify a credential file by providing a path to GoogleCredentials.
// Otherwise credentials are read from the GOOGLE_APPLICATION_CREDENTIALS environment
variable.
val credentials =
GoogleCredentials.fromStream(mContext.resources.openRawResource(R.raw.service_account_file))
.createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"))
val storage: Storage =
StorageOptions.newBuilder().setProjectId(projectID).setCredentials(credentials)
.build().service
println("Buckets:")
// Error at storage.lists()
val buckets: Page<Bucket> = storage.list()
for (bucket in buckets.iterateAll()) {
println(bucket.toString())
}
}
But on device it gives error like :
Error getting access token for service account:
Unable to resolve host "oauth2.googleapis.com": No address associated with hostname, iss:
xyz#texttospeech-12345.iam.serviceaccount.com
And on Emulator the error is :
xxxxxxxxx does not have storage.buckets.list access to the Google Cloud project.
Please let me know if you guys need something more.
Any suggestion will be appreciated
Thanks in Advance
Also if I run below command in Cloud SDK :
gcloud auth application-default login
I get this but I didnt understood what its trying to say
You can pass the credentials while creating the client connection.
TextToSpeechSettings settings = TextToSpeechSettings.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(authExplicit("JSON FILE PATH")))
.build();
try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create(settings)) {
// ... rest of your code
}
// ... rest of your code
And
public static GoogleCredentials authExplicit(String jsonPath) throws IOException {
GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(jsonPath))
.createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"));
return credentials;
}
GoogleCredentials imported from Google Auth Library For Java OAuth2 HTTP
N.B You need to make sure you are able to fetch the JSON file in your application.
I am developing an Android app and trying to implement a Google sign-in functionality. The authentication info that it's supposed to produce, is stored in my Firebase database. It seemed to have worked until recently.
I have been trying to resolve this frustrating exception in many ways:
I regenerated an API key on GCP, redownloaded google-services.json and rebuilt the project.
I noticed that the API key specified in the values.xml file (this file is stored in app\build\generated\res\google-services\debug\values) is outdated. Therefore, I tried to modify the fields "google_api_key" and "google_crash_reporting_api_key", as well as delete the file itself. The outdated data appears as soon as I rebuild the project.
I made sure that the SHA-1 is specified in the Firebase console
I set the API key's restrictions using GCP
Google sign-in code:
private void loginUserWithGoogle() {
GoogleSignInOptions gsio = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getOwnerActivity().getString(R.string.default_web_client_id))
.requestEmail()
.build();
GoogleSignInClient gsic = GoogleSignIn.getClient(getContext(), gsio);
Intent sii = gsic.getSignInIntent();
getOwnerActivity().startActivityForResult(sii, GOOGLE_SIGN_IN_REQUEST_CODE);
}
public void credentialsFromGoogleIntent(#Nullable Intent data) {
Task<GoogleSignInAccount> accountTask =
GoogleSignIn.getSignedInAccountFromIntent(data);
accountTask.addOnCompleteListener(receiveAuthInfo -> {
if (receiveAuthInfo.isSuccessful()) {
GoogleSignInAccount account = accountTask.getResult();
AuthCredential credential = GoogleAuthProvider.getCredential(account.getIdToken(), null);
auth.signInWithCredential(credential).
addOnCompleteListener(completeSignIn -> {
if (!completeSignIn.isSuccessful()) {
Log.w("LoginWindow", "signInWithCedential:Failure", completeSignIn.getException());
return;
}
CollectionReference reference = db.collection(USER_COLLECTION);
FirebaseUser user = auth.getCurrentUser();
Query query = reference.whereEqualTo("uuid", user.getDisplayName());
query.get().addOnCompleteListener(task -> {
if (task.getResult().size() <= 0) {
UserProfileChangeRequest.Builder build = new UserProfileChangeRequest.Builder();
String userId = UUID.randomUUID().toString();
build.setDisplayName(userId);
Task<Void> task1 = user.updateProfile(build.build());
Map<String, Object> userData = new HashMap<>();
userData.put("uuid", userId);
userData.put("password", "irrelevant");
Task<DocumentReference> task2 = reference.add(userData);
Tasks.whenAll(task1, task2).continueWith(taskContinue -> {
Intent intent = new Intent(getOwnerActivity(),
MainActivity.class);
getOwnerActivity().startActivity(intent);
return null;
});
} else {
Intent intent = new Intent(getOwnerActivity(),
MainActivity.class);
getOwnerActivity().startActivity(intent);
}
});
});
} else
Log.w("LoginWindow", receiveAuthInfo.getException().toString());
});
}
Although it's good that API Keys are set to expire, I hadn't realized that Firebase sets the API Keys that it creates to expire (yearly) until I investigated after reading your question.
If you haven't seen this, please see ... managing API keys for Firebase
You can view API keys for a project two ways:
Firebase Console, select your Project, click the 'gear' icon next to "Project Overview" and then "Project Settings".
Cloud Console, select your Project, then "APIs & Services" and then choose "Credentials".
The documentation doesn't appear to cover the renewal process.
I suspect (!?) that you can use e.g. Cloud Console, find the correct API Key and click "REGENERATE KEY".
Then you will need to revise all occurrences of this API Key in your distributed (!) code.
For example, I'm only using Web clients and I have:
const firebaseConfig = {
apiKey: "[[HERE]]",
authDomain: "...",
projectId: "...",
storageBucket: "..",
messagingSenderId: "...",
appId: "..."
};
I assume (!?) there's an equivalent config for Android apps.
Glad to inform you that the issue has been resolved. The troublemaker was an old version of the "google-services.json" in the "Build" folder. Removing and replacing it with the updated version of "google-services.json" worked like a charm.
I want to create a spreadsheet programmatically in android studio. how can I do that?
I used OAuth for signing in the user and now wants to create the spreadsheet in his drive folder.
I found the below code but don't know how to use it...
Spreadsheet spreadsheet = new Spreadsheet()
.setProperties(new SpreadsheetProperties()
.setTitle(title));
spreadsheet = service.spreadsheets().create(spreadsheet)
.setFields("spreadsheetId")
.execute();
System.out.println("Spreadsheet ID: " + spreadsheet.getSpreadsheetId());
For creating a Google Sheet in a programmatic way you should try to use Sheet API. More specifcally you need to use the create method.
If you are using the Google libraries for Java you could just try to follow the example in the create endpoint:
/*
* BEFORE RUNNING:
* ---------------
* 1. If not already done, enable the Google Sheets API
* and check the quota for your project at
* https://console.developers.google.com/apis/api/sheets
* 2. Install the Java client library on Maven or Gradle. Check installation
* instructions at https://github.com/google/google-api-java-client.
* On other build systems, you can add the jar files to your project from
* https://developers.google.com/resources/api-libraries/download/sheets/v4/java
*/
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.sheets.v4.Sheets;
import com.google.api.services.sheets.v4.model.Spreadsheet;
import java.io.IOException;
import java.security.GeneralSecurityException;
public class SheetsExample {
public static void main(String args[]) throws IOException, GeneralSecurityException {
// TODO: Assign values to desired fields of `requestBody`:
Spreadsheet requestBody = new Spreadsheet();
Sheets sheetsService = createSheetsService();
Sheets.Spreadsheets.Create request = sheetsService.spreadsheets().create(requestBody);
Spreadsheet response = request.execute();
// TODO: Change code below to process the `response` object:
System.out.println(response);
}
public static Sheets createSheetsService() throws IOException, GeneralSecurityException {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
// TODO: Change placeholder below to generate authentication credentials. See
// https://developers.google.com/sheets/quickstart/java#step_3_set_up_the_sample
//
// Authorize using one of the following scopes:
// "https://www.googleapis.com/auth/drive"
// "https://www.googleapis.com/auth/drive.file"
// "https://www.googleapis.com/auth/spreadsheets"
GoogleCredential credential = null;
return new Sheets.Builder(httpTransport, jsonFactory, credential)
.setApplicationName("Google-SheetsSample/0.1")
.build();
}
}
This example is not handling credentials and you will need to change this in order to work
In case you need more information in the libraries read through the Java Quickstart to work from there, also look at the java create method. And of course you can just do an HTTP request if you have already managed all the OAuth part.
I want to upload image on Google Cloud Storage from my android app. For that I searched and found that GCS JSON Api provides this feature. I did a lot of research for Android sample which demonstrates its use. On the developer site they have provided code example that only support java. I don't know how to use that API in Android. I referred this and this links but couldn't get much idea. Please guide me on how i can use this api with android app.
Ok guys so I solved it and got my images being uploaded in Cloud Storage all good.
This is how:
Note: I used the XML API it is pretty much the same.
First, you will need to download a lot of libraries.
The easiest way to do this is create a maven project and let it download all the dependencies required. From this sample project :
Sample Project
The libraries should be:
Second, you must be familiar with Cloud Storage using the api console
You must create a project, create a bucket, give the bucket permissions, etc.
You can find more details about that here
Third, once you have all those things ready it is time to start coding.
Lets say we want to upload an image:
Cloud storage works with OAuth, that means you must be an authenticated user to use the API. For that the best way is to authorize using Service Accounts. Dont worry about it, the only thing you need to do is in the API console get a service account like this:
We will use this service account on our code.
Fourth, lets write some code, lets say upload an image to cloud storage.
For this code to work you must put your key generated in step 3 in assets folder, i named it "key.p12".
I don't recommend you to do this on your production version, since you will be giving out your key.
try{
httpTransport= new com.google.api.client.http.javanet.NetHttpTransport();
//agarro la key y la convierto en un file
AssetManager am = context.getAssets();
InputStream inputStream = am.open("key.p12"); //you should not put the key in assets in prod version.
//convert key into class File. from inputstream to file. in an aux class.
File file = UserProfileImageUploadHelper.createFileFromInputStream(inputStream,context);
//Google Credentianls
GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(Collections.singleton(STORAGE_SCOPE))
.setServiceAccountPrivateKeyFromP12File(file)
.build();
String URI = "https://storage.googleapis.com/" + BUCKET_NAME+"/"+imagename+".jpg";
HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential);
GenericUrl url = new GenericUrl(URI);
//byte array holds the data, in this case the image i want to upload in bytes.
HttpContent contentsend = new ByteArrayContent("image/jpeg", byteArray );
HttpRequest putRequest = requestFactory.buildPutRequest(url, contentsend);
com.google.api.client.http.HttpResponse response = putRequest.execute();
String content = response.parseAsString();
Log.d("debug", "response is:"+response.getStatusCode());
Log.d("debug", "response content is:"+content);} catch (Exception e) Log.d("debug", "Error in user profile image uploading", e);}
This will upload the image to your cloud bucket.
For more info on the api check this link Cloud XML API
Firstly, You should get the below information by registering your application in the GCP console.
private final String pkcsFile = "xxx.json";//private key file
private final String bucketName = "your_gcp_bucket_name";
private final String projectId = "your_gcp_project_id";
Once you get the credentials, you should put the private key (.p12 or .json) in your assets folder. I'm using JSON format private key file. Also, you should update the image location to upload.
#RequiresApi(api = Build.VERSION_CODES.O)
public void uploadImageFile(String srcFileName, String newName) {
Storage storage = getStorage();
File file = new File(srcFileName);//Your image loaction
byte[] fileContent;
try {
fileContent = Files.readAllBytes(file.toPath());
} catch (IOException e) {
e.printStackTrace();
return;
}
if (fileContent == null || fileContent.length == 0)
return;
BlobInfo.Builder newBuilder = Blob.newBuilder(BucketInfo.of(bucketName), newName);
BlobInfo blobInfo = newBuilder.setContentType("image/png").build();
Blob blob = storage.create(blobInfo, fileContent);
String bucket = blob.getBucket();
String contentType = blob.getContentType();
Log.e("TAG", "Upload File: " + contentType);
Log.e("File ", srcFileName + " uploaded to bucket " + bucket + " as " + newName);
}
private Storage getStorage() {
InputStream credentialsStream;
Credentials credentials;
try {
credentialsStream = mContext.getAssets().open(pkcsFile);
credentials = GoogleCredentials.fromStream(credentialsStream);
} catch (IOException e) {
e.printStackTrace();
return null;
}
return StorageOptions.newBuilder()
.setProjectId(projectId).setCredentials(credentials)
.build().getService();
}
I'm trying to access a Purchase Status API from my ASP.NET web server using Google APIs .NET Client Library which is a recommended way for using Purchase API v1.1. However, the Authorization page of this API suggests direct web requests to Google's OAuth2 pages instead of using the corresponding client libraries.
OK, I tried both methods with all variations I could imagine and both of them lead to "The remote server returned an error: (400) Bad Request.".
Now what I've done to get to my point. First I've made all steps 1-8 under the Creating an APIs Console project of the Authorization page. Next I generated a refresh token as described there. During refresh token generation I chose the same Google account as I used to publish my Android application (which is in published beta state now).
Next I've created a console C# application for test purposes in Visual Studio (may be console app is the problem?)
and tried to call the Purchase API using this code (found in some Google API examples):
private static void Main(string[] args)
{
var provider =
new WebServerClient(GoogleAuthenticationServer.Description)
{
ClientIdentifier = "91....751.apps.googleusercontent.com",
ClientSecret = "wRT0Kf_b....ow"
};
var auth = new OAuth2Authenticator<WebServerClient>(
provider, GetAuthorization);
var service = new AndroidPublisherService(
new BaseClientService.Initializer()
{
Authenticator = auth,
ApplicationName = APP_NAME
});
var request = service.Inapppurchases.Get(
PACKAGE_NAME, PRODUCT_ID, PURCHASE_TOKEN);
var purchaseState = request.Execute();
Console.WriteLine(JsonConvert.SerializeObject(purchaseState));
}
private static IAuthorizationState GetAuthorization(WebServerClient client)
{
IAuthorizationState state =
new AuthorizationState(
new[] {"https://www.googleapis.com/auth/androidpublisher"})
{
RefreshToken = "4/lWX1B3nU0_Ya....gAI"
};
// below is my redirect URI which I used to get a refresh token
// I tried with and without this statement
state.Callback = new Uri("https://XXXXX.com/oauth2callback/");
client.RefreshToken(state); // <-- Here we have (400) Bad request
return state;
}
Then I tried this code to get the access token (I found it here: Google Calendar API - Bad Request (400) Trying To Swap Code For Access Token):
public static string GetAccessToken()
{
var request = WebRequest.Create(
"https://accounts.google.com/o/oauth2/token");
request.Method = "POST";
var postData =
string.Format(
#"code={0}&client_id={1}&client_secret={2}&redirect_uri={3}&grant_type=authorization_code",
// refresh token I got from browser
// also tried with Url encoded value
// 4%2FlWX1B3nU0_Yax....gAI
"4/lWX1B3nU0_Yax....gAI",
// ClientID from Google APIs Console
"919....1.apps.googleusercontent.com",
// Client secret from Google APIs Console
"wRT0Kf_bE....w",
// redirect URI from Google APIs Console
// also tried Url encoded value
// https%3A%2F%2FXXXXX.com%2Foauth2callback%2F
"https://XXXXX.com/oauth2callback/");
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length;
using (var dataStream = request.GetRequestStream())
{
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
}
try
{
// request.GetResponse() --> (400) Bad request again!
using (var response = request.GetResponse())
{
using (var dataStream = response.GetResponseStream())
{
using (var reader = new StreamReader(dataStream))
{
var responseFromServer = reader.ReadToEnd();
var jsonResponse = JsonConvert.DeserializeObject<OAuth2Response>(responseFromServer);
return jsonResponse.access_token;
}
}
}
}
catch (Exception ex) { var x = ex; }
return null;
}
So, to sum up all my long story:
Is it possible at all to pass OAuth2 authorization using either of methods above from a C# Console Application (without user interaction)?
I've double checked the redirect URI (since I saw a lot of discussed troubles because of it here on stackoverflow) and other parameters like ClientID and ClientSecret. What else I could do wrong in the code above?
Do I need to URL encode a slash in the refresh token (I saw that the first method using client library does it)?
What is the recommended way of achieving my final goal (Purchase API access from ASP.NET web server)?
I'll try to answer your last question. If you access your own data account, you dont need to use client id in oAuth2. Let's use service account to access Google Play API.
Create a service account in Google Developer Console > Your project > APIs and auth > Credentials > Create a new key. You will download a p12 key.
Create a C# project. You can choose console application.
Install google play api library from Google.Apis.androidpublisher. Nuget. You can find other library for dotnet in Google APIs Client Library for .NET
Link google api project with your google play account in API access
Authenticate and try to query information. I'll try with listing all inapp item. You can just change to get purchase's status
String serviceAccountEmail = "your-mail-in-developer-console#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"physical-path-to-your-key\key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://www.googleapis.com/auth/androidpublisher" }
}.FromCertificate(certificate));
var service = new AndroidPublisherService(
new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "GooglePlay API Sample",
});
// try catch this function because if you input wrong params ( wrong token) google will return error.
var request = service.Inappproducts.List("your-package-name");
var purchaseState = request.Execute();
// var request = service.Purchases.Products.Get(
//"your-package-name", "your-inapp-item-id", "purchase-token"); get purchase'status
Console.WriteLine(JsonConvert.SerializeObject(purchaseState));
You should do the following in your
private static IAuthorizationState GetAuthorization(WebServerClient client) method:
private IAuthorizationState GetAuthorization(WebServerClient client)
{
IAuthorizationState state = AuthState;
if (state != null)
{
return state;
}
state = new AuthorizationState()
{
RefreshToken = "4/lWX1B3nU0_Ya....gAI",
Callback = new Uri(#"https://XXXXX.com/oauth2callback/")
};
client.RefreshToken(state);
// Store and return the credentials.
HttpContext.Current.Session["AUTH_STATE"] = _state = state;
return state;
}
Let me know if it works for you.
Be aware that we know that the whole OAuth2 flow is awkward today, and we are working to improve it.