google drive Oauth 2.0 for java web application - heroku

My project is a java spark project that I have hosted on heroku. A part of the project requires that I download a particular file from my google drive into the application. I managed to set up everything when my application was running locally but as soon as I deployed the app it stopped working. This was regardless of the fact that when the app was running locally I used an Oauth2 client ID of type other and when I deployed the application I created a new one of type web application.
Below is a snippet of the authentication and download code:
public class AutoCalls {
/** Application name. */
private static final String APPLICATION_NAME = "calls-made";
private static final java.io.File DATA_STORE_DIR = new java.io.File(System.getProperty("target/classes/auth"), ".credentials/calls-made");
private static FileDataStoreFactory DATA_STORE_FACTORY;
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static HttpTransport HTTP_TRANSPORT;
private static final List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE);
static {
try {
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
} catch (GeneralSecurityException | IOException t) {
System.exit(1);
}
}
public static void startDownload() throws IOException, ParseException {
Drive serv = getDriveService();
// drive stuff deleted
}
public static Credential authorize() throws IOException {
InputStream in = new FileInputStream("target/classes/auth/client_secret_*****************************************.apps.googleusercontent.com.json");
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(DATA_STORE_FACTORY)
.setAccessType("offline")
.build();
Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
System.out.println("Credentials saved to " + DATA_STORE_DIR.getAbsolutePath());
return credential;
}
public static Drive getDriveService() throws IOException {
Credential credential = authorize();
return new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
}
}
Currently when I try to initiate the download I get a URL in the Heroku logs a follows:
2017-02-20T10:32:28.656820+00:00 app[web.1]: Please open the following address in your browser:
2017-02-20T10:32:28.656908+00:00 app[web.1]: https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=************-vvdi7u6bmp1vc90sdidtnuiftdi1t49c.apps.googleusercontent.com&redirect_uri=http://localhost:48085/Callback&response_type=code&scope=https://www.googleapis.com/auth/drive
When I try open the URL in a browser I get an error:
Error: redirect_uri_mismatch
I cannot find any good step by step tutorial on accessing google drive contents on a java web application so any help would be arreiciated.

Generally, if OAuth works on server-A but not on server-B, it's because the redirect URL hasn't been configured correctly on the API Console. There is no reason why you would need to use a different client-ID because a single client-ID can be configured with multiple URLs, eg your local host and your heroku server. Another possibility is the use of https.
There is a separate problem in your code where you iterate on lista.isEmpty(). You should be iterating on nextPageToken != null. See the answer to Google drive rest API, Download files from root folder only

Related

OAuth2.0 authorization spring boot web application flow

i am using java library client for web application authentication, i produce authorization url using client secret and client id,also i provided a redirect url within google api console,but i don't know if it is necessary for me to create this server to receive refresh token?
i mean in production i should provide a separate server to receive the refresh token?(redirect url comes to this server)
the main problem is user should paste the produced url on browser by himself but i want to open browser authmaticly , the second one is about reciving the refresh token i am not sure about creating another server to recieve refreshcode and i can't use service accounts i am going with web flow authentication.
UserAuthorizer userAuthorizer =
UserAuthorizer.newBuilder()
.setClientId(ClientId.of(clientId, clientSecret))
.setScopes(SCOPES)
.setCallbackUri(URI.create(OAUTH2_CALLBACK_URL_CONFIGURED_AT_GOOGLE_CONSOLE))
.build();
baseUri = URI.create("http://localhost:" + simpleCallbackServer.getLocalPort());
System.out.printf(
"Paste this url in your browser:%n%s%n",
userAuthorizer.getAuthorizationUrl(loginEmailAddressHint, state, baseUri));
and this is local server to receive refresh token:
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();
}
}
for those who struggling to get authorized using google oauth2.0 with spring boot
you cant redirect user to authorization url(which google authorization server gives to using your client id and client secret) use a controller to redirect user:
#GetMapping(value = "/redirect-user")
public ResponseEntity<Object> redirectToExternalUrl() throws URISyntaxException {
String url=gs.createUserAuthorizationUrl();
URI authorizationUrl = new URI(url);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(authorizationUrl);
return new ResponseEntity<>(httpHeaders, HttpStatus.FOUND);
}
at service layer createUserAuthorizationUrl() method is like below:
public String createUserAuthorizationUrl() {
clientId = "client-id";
clientSecret = "client-secret-code";
userAuthorizer =
UserAuthorizer.newBuilder()
.setClientId(ClientId.of(clientId, clientSecret))
.setScopes(SCOPES)
.setCallbackUri(URI.create("/oauth2callback"))
.build();
baseUri = URI.create("your-app-redirect-url-configured-at-google-console" + "your-spring-boot-server-port"); //giving redirect url
String redirectURL = userAuthorizer.getAuthorizationUrl(loginEmailAddressHint, state, baseUri).toString();
return redirectURL;
}
and let's create a controller to support the get request comming from google authorization server with an code. we are going to use that code to get access token from google.i get state and code by #RequestParam
and i also want to redirect user to my application.
#GetMapping(value = "/oauth2callback")
public ResponseEntity<Object> proceedeTOServer(#RequestParam String state,
#RequestParam String code) throws URISyntaxException {
String url="my-application-url-to-redirect-user";
URI dashboardURL = new URI(url);
HttpHeaders httpHeaders=new HttpHeaders();
httpHeaders.setLocation(dashboardURL);
gs.getCode(state,code);
return new ResponseEntity<>(httpHeaders,HttpStatus.FOUND);
}
and in getCode(code) in service layer i am going to send to code and receive the refresh token or access token:
UserCredentials userCredentials =userAuthorizer.getCredentialsFromCode(code, "your-app-redirect-url-configured-at-google-console" + "your-spring-boot-server-port");

Redirect Google Oauth2.0 LOCALHOST:9696/Callback On live Tomcat Server

I'm working on implementing Google drive API v3 in java spring boot. The application works fine on my local machine. I deployed the application in TOMCAT which is running on our UAT server for testing. I am following the procedure to Authorize my Google Drive API i got the consent window. I selected all the permissions that i require to access API and clicked allow button but right after that it redirected me to https://localhost:9696/callback URL which gives me page not found. I am stuck till here. do i need to provide my domain instead of default localhost has callback URL?
```
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
// Load client secrets.
InputStream in = GoogleDriveService.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
if (in == null) {
throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
}
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline").build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(9696).build();
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}
Implementation Ref. API Doc:-
https://developers.google.com/drive/api/v3/quickstart/java?hl=en&authuser=2

Server side authorization with Google Play Developer API?

Authorization is required to fetch information from the Google Play Developer API.
I know how to do this with Postman, but implementing authorization is much more cumbersome (redirect url, handling redirects, and so on...)
These would be the steps when you already have setup the auth data inside the Google Developer API Console.
1.) GET https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=http://www.myurl.com/oauth2callback&client_id=1234567890.apps.googleusercontent.com
2.) get code which was sent to redirect url.
3.) POST https://accounts.google.com/o/oauth2/token
with
grant_type:authorization_code
code:[the code I got before]
client_id:1234567890.apps.googleusercontent.com
client_secret:[my client secret]
4.) Invoke GET https://www.googleapis.com/androidpublisher/v2/applications/packageName/purchases/subscriptions/subscriptionId/tokens/token
with:
Scope: https://www.googleapis.com/auth/androidpublisher
and:
access_token as query parameter I got before.
Now I want to do all this programmatically. Obviously not so easy. I thought the Google API Client Libraries will help, but I don't see, how these lib can help me with my use case.
For example classes like GoogleAuthorizationCodeFlow expect a user id at the moment of the request, but I not necessarily have one at this moment, so I wonder how this API should be used in a clean way.
Is there a clean way to handle OAuth2.0 easier / programmatically with some API to access Google Play Developer API? Otherwise I must implement it manually.
After lots of headache (like always with Google APIs and services) I figured out how one can access Google Play Developer API information (like billing) by using existing APIs.
1.) Create in Developer API Console a service account (JSON) key:
2.) Download this service-account-private-key.json file (don't mistake it with the OAuth2.0 client secret file!).
3.) In Google Play Developer Console go to Settings -> Users & Permissions -> Invite New User and set as user e-mail of the new user the client_email from the downloaded file. Assign the access rights you want to give to this users via the checkboxes inside this view (for example 'View financial data').
4.) Add the proper dependency to your project (version ...-1.23.0 does not work for me):
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-androidpublisher</artifactId>
<version>v2-rev50-1.22.0</version>
</dependency>
5.) Load the service-account-private-key.json file into your application. In my case it's a webserver:
#Singleton
#Startup
public class WebserverConfiguration
{
private String serviceAccountPrivateKeyFilePath;
/** Global instance of the HTTP transport. */
public static HttpTransport HTTP_TRANSPORT;
/** Global instance of the JSON factory. */
public static JsonFactory JSON_FACTORY;
private GoogleCredential credential;
#PostConstruct
public void init()
{
assignServiceAccountFileProperty();
initGoogleCredentials();
}
public String getServiceAccountPrivateKeyFilePath()
{
return serviceAccountPrivateKeyFilePath;
}
public GoogleCredential getCredential()
{
return credential;
}
private void initGoogleCredentials()
{
try
{
newTrustedTransport();
newJsonFactory();
String serviceAccountContent = new String(Files.readAllBytes(Paths.get(getServiceAccountPrivateKeyFilePath())));
InputStream inputStream = new ByteArrayInputStream(serviceAccountContent.getBytes());
credential = GoogleCredential.fromStream(inputStream).createScoped(Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER));
}
catch (IOException | GeneralSecurityException e)
{
throw new InitializationException(e);
}
}
private void newJsonFactory()
{
JSON_FACTORY = JacksonFactory.getDefaultInstance();
}
private void assignServiceAccountFileProperty()
{
serviceAccountPrivateKeyFilePath = System.getProperty("service.account.file.path");
if (serviceAccountPrivateKeyFilePath == null)
{
throw new IllegalArgumentException("service.account.file.path UNKNOWN - configure it as VM startup parameter in Wildfly");
}
}
private static void newTrustedTransport() throws GeneralSecurityException, IOException
{
if (HTTP_TRANSPORT == null)
{
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
}
}
}
6.) Now I am able the fetch Google Play Developer API information, e.g. reviews:
private void invokeGoogleApi() throws IOException
{
AndroidPublisher publisher = new AndroidPublisher.Builder(WebserverConfiguration.HTTP_TRANSPORT, WebserverConfiguration.JSON_FACTORY, configuration.getCredential()).setApplicationName("The name of my app on Google Play").build();
AndroidPublisher.Reviews reviews = publisher.reviews();
ReviewsListResponse reviewsListResponse = reviews.list("the.packagename.of.my.app").execute();
logger.info("review list response = " + reviewsListResponse.toPrettyString());
}
This worked.
I cannot test it yet, but I'm sure that fetching the billing information works as well:
private SubscriptionPurchase getPurchase() throws IOException
{
AndroidPublisher publisher = new AndroidPublisher.Builder(WebserverConfiguration.HTTP_TRANSPORT, WebserverConfiguration.JSON_FACTORY, configuration.getCredential()).setApplicationName("The name of my app on Google Play").build();
AndroidPublisher.Purchases purchases = publisher.purchases();
SubscriptionPurchase purchase = purchases.subscriptions().get("the.packagename.of.my.app", "subscriptionId", "billing token sent by the app").execute();
//do something or return
return purchase;
}
There are complete code samples and documentation for doing this in Java here
In the Java source code this authorizes like this
private static Credential authorizeWithServiceAccount(String serviceAccountEmail)
throws GeneralSecurityException, IOException {
log.info(String.format("Authorizing using Service Account: %s", serviceAccountEmail));
// Build service account credential.
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(serviceAccountEmail)
.setServiceAccountScopes(
Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER))
.setServiceAccountPrivateKeyFromP12File(new File(SRC_RESOURCES_KEY_P12))
.build();
return credential;
}

Accessing Google Drive API using service account

I'm trying to access Google Drive API using a service account. I had set up the service account to access Google Directory API and I assume I can use the same for this. Here is my code, it's pretty much self-explanatory.
private static List<File> retrieveAllFiles(Drive service) throws IOException {
List<File> result = new ArrayList<File>();
Files.List request = service.files().list();
do {
FileList files = request.execute();
result.addAll(files.getItems());
request.setPageToken(files.getNextPageToken());
} while (request.getPageToken() != null &&
request.getPageToken().length() > 0);
return result;
}
public static Drive getDriveService(){
GoogleDriectoryService GDservice = new GoogleDriectoryService();
java.io.File serviceAccountP12Certificate = GDservice.loadF("resources/APIProject-62d885cdf671.p12");
HttpTransport httpTransport = new NetHttpTransport();//?
JacksonFactory jsonFactory = new JacksonFactory();//?
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(serviceAccountEmailAddress)
.setServiceAccountScopes(Arrays.asList(DriveScopes.DRIVE))
.setServiceAccountUser(serviceAccountUser)
.setServiceAccountPrivateKeyFromP12File(serviceAccountP12Certificate)
.build();
Drive service = new Drive.Builder(httpTransport, jsonFactory, credential)
.setApplicationName("radiantlogic-vds").build();
return service;
}
public static void main(String[] args){
Drive service = getDriveService();
List<File> files = retrieveAllFiles(service);
System.out.println(files);
}
But, I'm getting "com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized" at request.execute() line. Any help is appreciated.

sheets api error access denied exception desp Requested client not authorized

Hi I am using Google apis.. I have successfully able to access google drive and google calendar but when i try to access google spreadsheet i am getting following exception.
Exception in thread "main" com.google.gdata.util.AuthenticationException: Failed to refresh access token: 403 Forbidden
{
"error" : "access_denied",
"error_description" : "Requested client not authorized."
}
Caused by: com.google.api.client.auth.oauth2.TokenResponseException: 403 Forbidden
{
"error" : "access_denied",
"error_description" : "Requested client not authorized."
}
My code is as follows
private static Credential authorize() throws Exception {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
String SERVICE_ACCOUNT_EMAIL = "xxx#developer.gserviceaccount.com";
List<String> SCOPES = Arrays.asList("https://spreadsheets.google.com/feeds/");
GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
.setJsonFactory(jsonFactory).setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(SCOPES)
.setServiceAccountPrivateKeyFromP12File(
new java.io.File("xxx.p12"))
.setServiceAccountUser("xx#zzz.com")
.build();
return credential;
}
public static void main(String[] args) throws Exception {
Credential credential = authorize();
SpreadsheetService service = new SpreadsheetService("new data service ");
service.setProtocolVersion(SpreadsheetService.Versions.V3);
service.setOAuth2Credentials(credential);
// Define the URL to request. This should never change.
URL SPREADSHEET_FEED_URL = new URL(
"https://spreadsheets.google.com/feeds/spreadsheets/private/full");
// Make a request to the API and get all spreadsheets.
SpreadsheetFeed feed = service.getFeed(SPREADSHEET_FEED_URL, SpreadsheetFeed.class);
List<SpreadsheetEntry> spreadsheets = feed.getEntries();
// Iterate through all of the spreadsheets returned
for (SpreadsheetEntry spreadsheet : spreadsheets) {
// Print the title of this spreadsheet to the screen
System.out.println(spreadsheet.getTitle().getPlainText());
}
}
Thanks
I was only passing the spreadsheet scope, I have to pass both drive and sheet scope. Now I am able to read spreadsheets from drive.. Here is the corrected code.
String SERVICE_ACCOUNT_EMAIL = "xxxx#developer.gserviceaccount.com";
ArrayList<String> scopes = new ArrayList<String>();
scopes.add(0, DriveScopes.DRIVE);
scopes.add(1, "https://spreadsheets.google.com/feeds");
GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
.setJsonFactory(jsonFactory).setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(scopes)
.setServiceAccountPrivateKeyFromP12File(
new java.io.File("xx.p12"))
.setServiceAccountUser("yyy#example.com")
.build();

Resources