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

Began work on tests, service credentials #1

Merged
merged 2 commits into from
Sep 19, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}