Como conectar nuestra aplicación node.js con Google Drive

Google Drive y NodeJs

1. Introducción

Aunque hace unos meses implementamos en nuestra solución de gestión de empresas un módulo de gestor de ficheros, nos encontramos con una situación en la cual se quiere montar una infraestructura completa para una empresa, en la cual proponemos el uso de Google G Suite para negocios, donde entre otras cosas se utiliza Google Drive como solución de almacenamiento compartido en la nube.

Se requiere el poder compartir documentación y facturas con clientes de la empresa mediante un pequeño portal adherido a nuestra aplicación de gestión. Utilizar nuestro propio repositorio obligaría al usuario adjuntar al sistema los ficheros que ya están en la nube, por lo que se propone que nuestra aplicación en NodeJs acceda directamente a Google Drive para servirlos.

diagrama acceso google drive

2. Alternativas descartadas

  • Utilizar dropbox. Sinceramente, no somos expertos en trabajar con la Google API, la cual en su v3 no dispone de demasiada documentación y se nos hace complicada con sus constantes limitaciones. Ya hemos trabajado con la API de Dropbox y es muy sencillo. Ademas Dropbox es más rápido que Google Drive. Pero el ofrecer agenda, correo y almacenamiento en una misma solución aportaba un alto valor añadido.
  • Que sea nuestro cliente Web el que se conecte a Drive, liberando al servidor. Google no permite crear credenciales de acceso a datos de una Aplicación mediante Web/Javascript (“Application data cannot be accessed securely from a web browser. Please consider selecting another platform.”).
  • Mostrar la carpeta de Drive en un IFRAME. Sin utilizar la API,  la URL de la misma en un IFRAME. Es una solución sucia que ha usado gente pero a cualquier cambio de la web dejaría de funcionar.

3. Sobre la autenticación de Google

Cualquier acceso que se quiera hacer a datos almacenados en Google (Drive, Calendar, GMail, etc…) a través de algunas de sus APIs se ha de hacer mediante OAuth 2.0 y siempre en un contexto de doble validación. Lo estándar es incorporar la identificación de nuestra aplicación y proporcionar interfície por la que, después de que el usuario acceda a permitir el acceso (La típica pantalla de “¿Desea permitir que XXX acceda a sus contactos, etc..?”), Google se conecte a nuestra aplicación y proporcione un token temporal de acceso. Esto es así para aplicaciones de Google Drive, Apps de Android o Extensiones de Chrome.

Luego está la Google API Scopes, que son los permisos que ha de tener una App y el usuario permitir. Por ejemplo, si necesitamos solo leer a los metadatos de nuestros ficheros en Drive se ha de solicitar permiso para el scope (se escribe en forma de URL: https://www.googleapis.com/auth/drive.metadata.readonly). Al instalar por ejemplo la App al usuario se le pedirá que autorice ese permiso.

En nuestro caso no queremos involucrar al usuario (cliente externo) porque nuestra aplicación no quiere acceder sus datos, sino a los de la propia aplicación, ni siquiera que tenga cuenta Google. Para ello se ha de crear una cuenta de servicio donde el servicio es nuestra aplicación. Los datos de la aplicación también están en Google pero en una especie de cuenta fictícia. Y solo es posible (hasta donde he podido descubrir) que esa cuenta que permite a un servicio acceder a los datos pertenezca a G Suite. Una buena jugada para obligar a contratar una G Suite (antiguo Google for business) y no permitir a un desarrollador usar su propia cuenta Google para almacenar datos de sus aplicaciones y ofrecer funcionalidad a terceros sin que nadie pase por caja.

4. Pasos para acceder con NodeJs

4.1. Como desarrollador, crear un proyecto y activar la API

Desde nuestra propia cuenta google, hay que acceder a la consola de desarrolladores (https://console.developers.google.com/) crear un proyecto que llamaremos BizlogicGinDriverTest.

Creacion de proyecto en la consola de desarrolladores de Google

Activar la API para este proyecto

Activar API en proyecto Google

Y de, entre los cientos de APIs que ofrece Google, seleccionar la de Drive y activarla con el botón Enable

Activar API de Drive en la consola de desarrollador

4.2. Crear servicio y sus credenciales en nuestro proyecto

En el apartado Credentials, hay que crear credenciales del tipo Service Account

creacion de credenciales de servicio

E informar de los datos de este servicio:

  • Nombre: hemos escogido BizlogicGinDriverTestService
  • Role: De entre todos los disponibles, hemos escogido Storage Admin para control total de los elementos de Drive
  • Service Account ID: Dejamos el propuesto por defecto
  • Tipo de clave: Fichero JSON
  • Hemos de marcar Enable G Suite Domain-wide Delegation. Sin activar esto, no será posible permitir el acceso a una G Suite.

Creacion de servicio google

Y después de crearla generar una clave con Create Key.

A los pocos segundos se descarga un fichero (BizlogicGinDriverTest-4002249dd70d,json) con los datos para que nuestro servicio se identifique, incluyendo las claves privadas. Este fichero ha de estar custodiado siempre de forma segura y no incluido en el código fuente. Ya que si fuese sustraído cualquiera podría montar una aplicación que suplantara a la nuestra y acceder a los mismos datos.

{
  "type": "service_account",
  "project_id": "bizlogicgindrivertest",
  "private_key_id": "8dcae6048b605a7f3d3c78e468383b0324e89e8b",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ.....",
  "client_email": "bizlogicgindrivertestservice@bizlogicgindrivertest.iam.gserviceaccount.com",
  "client_id": "109105701127609333094",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://accounts.google.com/o/oauth2/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/bizlogicgindrivertestservice%40bizlogicgindrivertest.iam.gserviceaccount.com"
}

4.3. Crear una Google Suite si no está creada

Como hemos dicho, este acceso mediante servicio parece solo funcionar con cuentas Business que tienen un panel de control. Los detalles de como crear estas suites para empresas queda fuera del alcance de post, pero nosotros hemos creado una para la empresa BizlogicTest y asociada a un dominio nuestro (y sin relación con la cuenta de desarrollador que hemos utilizado). Tenemos 15 días de prueba sin que necesitemos informar de la información de facturación, pudiéndola cancelar antes de ese momento.

4.4. Autorizar al servicio en G Suite

Nos dirigimos al panel de administracion que se encuentra en https://admin.google.com/NUESTRO_DOMINIO.com

Panel de administracion de G Suite

Nos dirigimos a Security -> Api Reference  y marcamos Enable API Access. Luego dentro de Security hacemos click en “show more” -> Advanced Settings y vamos al apartado Manage API client access, que es donde daremos los accesos a nuestro servicio.

Autorización a nuestro servicio

En esta pantalla añadimos autorización a nuestro servicio (que se identifica con el valor client_id que encontramos el JSON descargado antes o en el panel de desarrollador), e indicamos el scope, que será acceso total a Drive. Si al autorizar da un mensaje de “this client has not beed created on Google” es que hemos hecho algo mal en los puntos anteriores.

4.5. Añadir manualmente un fichero al Drive del G Suite

Para seguir con la prueba de concepto vamos a acceder, validado como admin o otro usuario de nuestra G Suite a Drive y vamos a subir un fichero llamado neron.jpg, el cual compartiremos con todos los usuarios de nuestra empresa ficticia.

Google Drive de G Suite

4.6. Acceder mediante node.js

Vamos a programar por fin.

Desde hace no mucho Google ha liberado las librerias para acceso a su API en lenguaje node.js. Así ya no hará falta usar algunos de los distintos paquetes publicados en NPM para acceder a la API (y con los cambios que han hecho en la API últimamente imagino que habrán dejado de funcionar) y podemos utilizar el paquete “oficial” de Google. Los ejemplos de la página de Git-Hub están para la v2 de la API pero nosotros hemos decidido trabajar con la V3 (para poder trabajar en el futuro con la nueva feature de Team Drives).

El acceso a la API de Google, aunque se haga como servicio, se hace con identificación y obtención de un token temporal. Para ello se ha de crear un JSON Web Token (JWT) en el que se informan de los datos que autentifican la aplicación y devuelven un token temporal como este:

{
"access_token": "ya29.ElkPBIgKo45YobQLq3OFcwDs_EQ1w6ajfJ3KlK-A4a7cz5m0pqdBP-Mc8vY2p4eHMS99UEtwcDQumetxqocIevkAv0ibHivD4LIUuzBKfbtrh0cBeBNGWAmJsA",
"token_type": "Bearer",
"expiry_date": 1489597220000,
"refresh_token": "jwt-placeholder"
}

Este token hemos comprobado que tiene una validez de una hora ya que lo hemos pedido a las 17:00 del 15 de marzo y con ayuda de la consola de javascript la fecha de expiración es esta:

new Date(1489597220000)
Wed Mar 15 2017 18:00:20 GMT+0100 (CET)

Ahora ya sabemos obtener el token para que nuestro servicio pueda interactuar. En el siguiente código completo nos autenticamos, listamos los ficheros de Drive, para cada uno lo obtenemos y mostramos algo de información, y finalmente creamos un nuevo fichero. Obsérvese como en cada petición se acompaña el token de autorización.

//Instalación normal mediante NPM
var googleapis = require('googleapis');
var googleAuth = require('google-auth-library');
var fs = require('fs');

var drive = googleapis.drive('v3');


//Leer fichero privado con credenciales del servicio
var credenciales = JSON.parse(fs.readFileSync('BizlogicGinDriverTest-8dcae6048b60.json', 'utf8'));

//Crear objeto de autorización JWT
var jwtClient = new googleapis.auth.JWT(
		credenciales.client_email, // bizgindrive3@bizlogicgindrive.iam.gserviceaccount.com
		null,
		credenciales.private_key,  // -----BEGIN PRIVATE KEY-----\nMIIEvQIBA......
		['https://www.googleapis.com/auth/drive']);

//Solicitar token autorización
jwtClient.authorize(function (err, tokens) {
if (err) {
	console.error('Error de autorización: ' + err);
	return;
}
console.log('Autorización correcta para cliente ' + credenciales.client_id);
	// Hacer petición autorizada para listar ficheros
drive.files.list({
		auth: jwtClient//,
		,corpora: 'user'
			,supportsTeamDrives: true,
			includeTeamDriveItems: true//,
			,spaces: 'drive'
				//q: 'supportsTeamDrives=true'
	}, function (err, resp) {
		if (err) {
			console.error('Ha habido un error en la petición: ' + err);
			return;
		}

		console.log('Petición correcta. Obteniendo ficheros:');

		for(var i=0; i<resp.files.length; i++){
			var fileItem = resp.files[i];

			drive.files.get({auth: jwtClient, fileId: fileItem.id }, function (err, resp){
				if (err) {
					console.error('Ha habido un error obteniendo el fichero: ' + err);
					return;
				}

				console.log('----------------------------' );
				console.log('Id:        ' + resp.id);
				console.log('Nombre:    ' + resp.name);
				console.log('MIME type: ' + resp.mimeType);
			});
		}

		// Crear un fichero de texto
		drive.files.insert({
			resource: {
				title: 'Test',
				mimeType: 'text/plain'
			},
			media: {
				mimeType: 'text/plain',
				body: 'Hola'
			},
			auth: jwtClient
		}, function (err, response) {
			console.log('error:', err, 'inserted:', response.id);
		}); // insert

	}); // list
}); // authorize

La salida de la consola durante la ejecución es esta:

Autorización correcta para cliente 110077985616288446940
Petición correcta. Obteniendo ficheros:
----------------------------
Id: 0B_-hYwhu28sfX2hCNTg1bVhkOFk
Nombre: Getting started
MIME type: application/pdf
error: null inserted: 0B_-hYwhu28sfenhqV1FNRm5UX0k

Vemos que la inserción del fichero ha sido correcta, pero al listar los contenidos de Drive vemos el único fichero que hay por defecto en cualquier carpeta Drive nueva: el manual en PDF, y no está neron.jpg.

4.7 ¿Donde estan los ficheros?

Despues de varias pruebas ni los ficheros creados mediante la API estan accesibles si no es con la API, ni los ficheros de los usuarios de G Suite se pueden acceder si no es con la aplicación Web o el cliente Drive. Podemos probar todos parámetros de la API en la conexión, en el listado de archivos, la creación, o permisos pero en Google Drive están completamente estancos el espacio de usuarios y de servicios.

Los ficheros vistos y subidos mediante la API se encuentran en la cuenta fictícia del servicio de la API (bizlogicgindrivertestservice@bizlogicgindrivertest.iam.gserviceaccount.com) y no lo ven los usuarios ni podemos acceder mediante la interficie Web o el cliente de Google Drive.

Si queremos que nuestro servicio y el resto de usuarios de G Suite puedan compartir información, solo hay dos maneras:

  • Mediante Team Drives. Son algo parecido a unidades compartidas dentro de Drive, creado para solventar este tipo de problemas. Por desgracia esta funcionalidad se está implantando justo en marzo de 2017 y nuestra cuenta G Suite no dispone de ella todavía (se debe estar implantando por paises).
  • Crear desde el servicio una carpeta y compartirla con el resto de usuarios.

Para hacer esto último el código seria el siguiente:

// Crear carpeta
var fileMetadata = {
		'name' : 'Invoices',
		'mimeType' : 'application/vnd.google-apps.folder'
};

drive.files.create({
	resource: fileMetadata,
	fields: 'id',
	auth: jwtClient
}, function(err, file) {
	if(err) {
		// Handle error
		console.error('Error creando carpeta: ' + err);
	} else {
		console.log('Carpeta creada con Id: ', file.id);
		var userPermission = {
			'type': 'user',
			'role': 'writer',
			'emailAddress': 'dani@disier.com'
		};
		drive.permissions.create({
			resource: userPermission,
			fileId: file.id,
			fields: 'id',
			auth: jwtClient
		}, function(err, res) {
			if (err) {
				// Handle error
				console.error('Error creando permiso: ' + err);
			} else {
				console.log('Permission creado: ' + res.id + ' al elemento con id: ' + file.id);
			}
		});
	}
});

Una vez ejecutado este código ya podremos acceder a la carpeta, que veremos en “compartido conmigo” y en detalles quien es el creador de la misma:

Carpeta creada mediante la API y compartida con un usuario

Para compartirla con todos los usuarios se tendría que crear un permiso de tipo ‘domain’ e indicar el dominio de la empresa.

5. Siguientes pasos

Visto un sencillo ejemplo de listar, descargar y subir ficheros. A partir de aqui las posibilidades son enormes, ya que la API que ofrece Google es inmensa y cuya referencia de la V3 de la API nos da toda la información que necesitamos:

https://developers.google.com/drive/v3/reference

Para nuestra solución el camino escogido es:

  • Crear con la API una carpeta llamada “Documentación Cliente”
  • Compartir la carpeta con todo el dominio.
  • Para cada cliente crear una subcarpeta NIF o CIF, donde los usuarios podrán dejar documentación accesible.
  • Nuestro portal tendrá acceso a esas carpetas y creará los ficheros subidos por cada cliente para que estén a disposición de los usuarios.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.