Skip to content
This repository has been archived by the owner on Mar 8, 2019. It is now read-only.

Commit

Permalink
Merge pull request #1 from dstieglitz/master
Browse files Browse the repository at this point in the history
Began work on tests, service credentials
  • Loading branch information
donbeave committed Sep 19, 2015
2 parents 128637c + d61418f commit 0f1b406
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 10 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
grails-google-drive [![Build Status](https://travis-ci.org/donbeave/grails-google-drive.svg?branch=master)](https://travis-ci.org/donbeave/grails-google-drive)
===================

Grails Google Drive Plugin
## Credentials setup

Setup credentails from Google Developer Console. This plugin supports "Web" credentials and "Service" (non-interactive) credentials. Download the JSON credentials file from the Google console and point the config attribute `google.drive.credentials.filePath` to point to the file. You should also specify the type of credentials using `google.drive.credentials.type` with values `web` or `service`.

## Scopes

Specify scopes using `google.drive.scopes` and the accepted string values provided by Google. See https://developers.google.com/drive/web/scopes




Copyright and license
---------------------

Copyright 2013-2014 Alexey Zhokhov under the [Apache License, Version 2.0](LICENSE). Supported by [Polusharie][polusharie].
Copyright 2013-2015 Alexey Zhokhov and Dan Stieglitz under the [Apache License, Version 2.0](LICENSE). Supported by [Polusharie][polusharie].

[polusharie]: http://www.polusharie.com
2 changes: 1 addition & 1 deletion grails-app/conf/BuildConfig.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ grails.project.dependency.resolution = {
compile 'com.google.oauth-client:google-oauth-client-jetty:1.18.0-rc', {
excludes 'httpclient', 'junit'
}

compile 'org.apache.tika:tika-core:1.5', {
excludes 'xercesImpl', 'xmlParserAPIs', 'xml-apis', 'groovy'
}
compile 'org.bouncycastle:bcprov-jdk16:1.46'
}

plugins {
Expand Down
6 changes: 5 additions & 1 deletion grails-app/conf/DefaultGoogleDriveConfig.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@
google.drive.enabled = true
google.drive.credentials.path = System.getProperty('catalina.base') ?
"${System.getProperty('catalina.base')}/data/oauth-credentials" :
"${System.getProperty('user.home')}/.oauth-credentials";
"${System.getProperty('user.home')}/.oauth-credentials";

google.drive.credentials.type='service'
google.drive.credentials.filePath='/Users/dstieglitz/Grails Google Drive Plugin-7b32fc08891a.json'
google.drive.scopes = ['https://www.googleapis.com/auth/drive']
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,71 @@ class GoogleDriveService {
def init() {
def config = grailsApplication.config.google.drive


if (config.enabled) {
if (!config.key) {
throw new RuntimeException('Google Drive API key is not specified')
}
if (!config.secret) {
throw new RuntimeException('Google Drive API secret is not specified')
switch (config.credentials.type) {
case 'web':
drive = getWebConfiguredDrive(config.credentials.filePath)
break;
case 'service':
drive = getServiceConfiguredDrive(config.credentials.filePath)
break;
}
drive = new GoogleDrive(config.key, config.secret, config.credentials.path, 'grails')
}
}

private def getWebConfiguredDrive(configFilePath) {
def config = grailsApplication.config.google.drive
def key = null
def secret = null
def jsonConfig = null

try {
def jsonFile = new java.io.File(configFilePath)
jsonConfig = JSONConfigLoader.getConfigFromJSON('web', jsonFile)
} catch (IOException e) {
log.error e.message
}

key = jsonConfig?.key ?: config.key
if (!key) {
throw new RuntimeException('Google Drive API key is not specified')
}

secret = jsonConfig?.secret ?: config.secret
if (!secret) {
throw new RuntimeException('Google Drive API secret is not specified')
}

return new GoogleDrive(key, secret, config.credentials.path, 'grails')
}

private def getServiceConfiguredDrive(configFilePath) {
def config = grailsApplication.config.google.drive
def key = null
def secret = null
def jsonConfig = null

try {
def jsonFile = new java.io.File(configFilePath)
jsonConfig = JSONConfigLoader.getConfigFromJSON('service', jsonFile)
} catch (IOException e) {
log.error e.message
}

key = jsonConfig?.email ?: config.key
if (!key) {
throw new RuntimeException('Google Drive API email is not specified for service account')
}

secret = jsonConfig?.privateKey ?: config.secret
if (!secret) {
throw new RuntimeException('Google Drive API private key is not specified for service account')
}

return new GoogleDrive(key, secret, config.scopes)
}

List<File> list() {
def files = drive.native.files().list().execute()

Expand Down
37 changes: 37 additions & 0 deletions src/groovy/org/grails/plugin/google/drive/JSONConfigLoader.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.grails.plugin.google.drive

import groovy.json.JsonSlurper

/** \
* Loads the JSON credential file downloaded from the Google Developers Console
* Created by dstieglitz on 9/18/15.
*/
class JSONConfigLoader {

public static Map getConfigFromJSON(String type, File jsonFile) throws IOException {
switch (type) {
case 'service':
return getConfigFromServiceFileJSON(jsonFile)
case 'web':
return getConfigFromWebApplicationJSONFile(jsonFile)
default:
throw new IOException("Invalid credential type specified: ${type}")
}
}

public static Map getConfigFromWebApplicationJSONFile(File jsonFile) throws IOException {
JsonSlurper slurper = new JsonSlurper()
def object = slurper.parseText(jsonFile.text)

return ['key' : object.web.client_id,
'secret': object.web.client_secret]
}

public static Map getConfigFromServiceFileJSON(File jsonFile) throws IOException {
JsonSlurper slurper = new JsonSlurper()
def object = slurper.parseText(jsonFile.text)

return ['email' : object.client_email,
'privateKey': object.private_key]
}
}
22 changes: 22 additions & 0 deletions src/java/org/grails/plugin/google/drive/GoogleDrive.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.googleapis.media.MediaHttpUploaderProgressListener;
import com.google.api.client.http.ByteArrayContent;
Expand All @@ -29,6 +30,7 @@
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.SecurityUtils;
import com.google.api.client.util.store.DataStore;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.drive.Drive;
Expand All @@ -42,8 +44,11 @@

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
* @author <a href='mailto:[email protected]'>Alexey Zhokhov</a>
Expand Down Expand Up @@ -103,6 +108,19 @@ static Drive init(String clientId, String clientSecret, String credentialsPath,
return new Drive(HTTP_TRANSPORT, JSON_FACTORY, credential);
}

static Drive init(String emailAddress, String privateKey, List<String> scopes) throws IOException, GeneralSecurityException {

GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(emailAddress)
.setServiceAccountPrivateKey(PrivateKeyUtil.readPrivateKey(privateKey))
.setServiceAccountScopes(scopes)
.build();

return new Drive(HTTP_TRANSPORT, JSON_FACTORY, credential);
}

static Credential authorize(String clientId, String clientSecret, String credentialsPath, String credentialStore,
HttpTransport httpTransport, JsonFactory jsonFactory) throws IOException {
GoogleClientSecrets.Details installedDetails = new GoogleClientSecrets.Details();
Expand Down Expand Up @@ -195,6 +213,10 @@ public GoogleDrive(String clientId, String clientSecret, String credentialsPath,
drive = init(clientId, clientSecret, credentialsPath, credentialStore);
}

public GoogleDrive(String emailAddress, String privateKey, List<String> scopes) throws IOException, GeneralSecurityException {
drive = init(emailAddress, privateKey, scopes);
}

private Drive drive;

public File uploadFile(java.io.File file) throws IOException {
Expand Down
70 changes: 70 additions & 0 deletions src/java/org/grails/plugin/google/drive/PrivateKeyUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.grails.plugin.google.drive;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import sun.misc.BASE64Decoder;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;

/**
* Created by dstieglitz on 9/18/15.
*/
public class PrivateKeyUtil {

static {
Security.addProvider(new BouncyCastleProvider());
}

public static PrivateKey readPrivateKey(File keyFile) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
// read key bytes
FileInputStream in = new FileInputStream(keyFile);
byte[] keyBytes = new byte[in.available()];
in.read(keyBytes);
in.close();

String privateKey = new String(keyBytes, "UTF-8");
privateKey = privateKey.replaceAll("(-+BEGIN RSA PRIVATE KEY-+\\r?\\n|-+END RSA PRIVATE KEY-+\\r?\\n?)", "");

// don't use this for real projects!
BASE64Decoder decoder = new BASE64Decoder();
keyBytes = decoder.decodeBuffer(privateKey);

// generate private key
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
}

// public static PrivateKey readPrivateKey(String keyString) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
// // read key bytes
// byte[] keyBytes = keyString.getBytes();
//
// String privateKey = new String(keyBytes, "UTF-8");
// privateKey = privateKey.replaceAll("(-+BEGIN RSA PRIVATE KEY-+\\r?\\n|-+END RSA PRIVATE KEY-+\\r?\\n?)", "");
//
// // don't use this for real projects!
// BASE64Decoder decoder = new BASE64Decoder();
// keyBytes = decoder.decodeBuffer(privateKey);
//
// // generate private key
// PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
// KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// return keyFactory.generatePrivate(spec);
// }

public static PrivateKey readPrivateKey(String keyString) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
PemReader reader = new PemReader(new StringReader(keyString));
PemObject pemObject = reader.readPemObject();
reader.close();
KeyFactory factory = KeyFactory.getInstance("RSA", "BC");
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(pemObject.getContent());
return factory.generatePrivate(privKeySpec);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package google.drive

import grails.test.spock.IntegrationSpec
import org.grails.plugin.google.drive.GoogleDriveService

class GoogleDriveServiceIntegrationSpec extends IntegrationSpec {

def googleDriveService

def setup() {
}

def cleanup() {
}

void "test configuration"() {
setup:
def list

when:
list = googleDriveService.list()

then:
list.size() > 0
}
}

0 comments on commit 0f1b406

Please sign in to comment.