Error al crear el evento del calendario de Google con una cuenta de servicio

Dec 19 2020

Tengo el requisito de crear un evento de calendario de Google en un calendario y agregar otros usuarios como asistentes a ese evento. El objetivo es enviar eventos del calendario a los usuarios de la aplicación sin su consentimiento (O-Auth).

Después de leer la documentación de Google, descubrí que necesito una cuenta de servicio. Así que creé un proyecto y una cuenta de servicio desde una de las direcciones de correo electrónico de nuestra G-Suite, [email protected] y habilité la API de calendario para la misma.

Creé y descargué un par de claves (JSON) cuyo contenido es,

{
  "type": "service_account",
  "project_id": "*****",
  "private_key_id": "bdbbcd**************49f77d599f2",
  "private_key": "**"
  "client_email": "******@*****.iam.gserviceaccount.com",
  "client_id": "11083******576856",
  "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/****dev%40*****kdev.iam.gserviceaccount.com"
}

Y según la documentación, procedí a escribir el código de flujo de autenticación,

public static GoogleCredential doOauth( String credsPath ) throws IOException
    {
        GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream(credsPath))
                .createScoped(Collections.singleton(CalendarScopes.CALENDAR));
        System.out.println(credential);
        return credential;
    }

El credentialobjeto tiene la mayoría de los detalles del archivo de claves. Sin embargo, los campos, serviceAccountUser, accessToken, refreshToken, clientAuthenticationy requestInitializertienen nullvalor. (Supongo que algo anda mal aquí)

Ahora, usando el credentialObject, continué escribiendo el código según la documentación para crear el evento.

GoogleCredential credential = doOauth(CREDENTIALS_FILE_PATH);
        Event event = new Event().setSummary("Google I/O 2015").setLocation("800 Howard St., San Francisco, CA 94103")
                .setDescription("A chance to hear more about Google's developer products.");
        final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();

        DateTime startDateTime = new DateTime("2020-12-28T09:00:00-07:00");
        EventDateTime start = new EventDateTime().setDateTime(startDateTime).setTimeZone("America/Los_Angeles");
        event.setStart(start);

        DateTime endDateTime = new DateTime("2020-12-28T17:00:00-07:00");
        EventDateTime end = new EventDateTime().setDateTime(endDateTime).setTimeZone("America/Los_Angeles");
        event.setEnd(end);

        String[] recurrence = new String[] { "RRULE:FREQ=DAILY;COUNT=2" };
        event.setRecurrence(Arrays.asList(recurrence));

        EventAttendee[] attendees = new EventAttendee[] { new EventAttendee().setEmail("[email protected]") };
        event.setAttendees(Arrays.asList(attendees));

        EventReminder[] reminderOverrides = new EventReminder[] {
                new EventReminder().setMethod("email").setMinutes(24 * 60),
                new EventReminder().setMethod("popup").setMinutes(10), };
        Event.Reminders reminders = new Event.Reminders().setUseDefault(false)
                .setOverrides(Arrays.asList(reminderOverrides));
        event.setReminders(reminders);
        String calendarId = "primary";
        Calendar service = new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
                .setApplicationName("testapp").build();
        event = service.events().insert(calendarId, event).execute();
        System.out.printf("Event created: %s\n", event.getHtmlLink());

Pero, esto resultó en el error,

{
  "code" : 403,
  "errors" : [ {
    "domain" : "calendar",
    "message" : "Service accounts cannot invite attendees without Domain-Wide Delegation of Authority.",
    "reason" : "forbiddenForServiceAccounts"
  } ],
  "message" : "Service accounts cannot invite attendees without Domain-Wide Delegation of Authority."
}

Después de pasar un tiempo Domain-Wide Delegation, entendí que esto es necesario si tenemos que enviar el evento como otro usuario desde nuestro g-suite, lo cual no es necesario para mi problema. Pero, para depurar, seguí adelante y proporcioné Domain-Wide Delegationy volví a ejecutar el programa. Volvió a ocurrir el mismo error.

Entonces, eliminé a los invitados / asistentes del eventobjeto y volví a ejecutar la aplicación. Esta vez, el programa se ha ejecutado sin ningún error, pero el enlace evento generado, al hacer clic dice, Could not find the requested event.

No veo ningún ejemplo de uso de la cuenta de servicio a través de las bibliotecas de cliente de Java en el enlace de desarrollador de Google.

¿Me pueden hacer saber qué está fallando aquí y la documentación oficial / de trabajo sobre cómo crear exactamente un evento de calendario de Google desde mi proyecto agregar otros usuarios (que no sean de G Suite también) para agregar como asistentes, para que yo no tenga para obtener el consentimiento de otros usuarios para agregar eventos a su propio calendario?

Gracias.

Respuestas

DaImTo Dec 21 2020 at 15:06

En primer lugar, quiero decir que esto es algo nuevo, si ves muchas preguntas y tutoriales que no indicaron que debías hacer esto, es porque las cuentas de servicio solían poder enviar invitaciones, esto es algo que Google bloqueó. Hace un año.

Esto debería funcionar, pero no lo he probado porque ya no tengo acceso a una cuenta de Gsuite. Debe hacer que el administrador de gsuite configure la delegación de todo el dominio a otro usuario. Luego, la cuenta de servicio debe hacerse pasar por ese usuario para que aparezca como ese usuario es el que envía las invitaciones.

El único ejemplo que tengo de cómo se agrega está en .net

.net ejemplo

var gsuiteUser = "[email protected]";
var serviceAccountCredentialInitializer = new ServiceAccountCredential.Initializer(serviceAccount)
            {
                User = gsuiteUser,
                Scopes = new[] { GmailService.Scope.GmailSend, GmailService.Scope.GmailLabels }

            }.FromCertificate(certificate);

Supongo de ejemplo de Java

No soy un desarrollador de Java, pero la biblioteca de cliente .net y la biblioteca de cliente de Java están muy cerca de cómo se desarrollaron. Supongo que está buscando un método llamado setServiceAccountUser

GoogleCredential credential = new  GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
                .setJsonFactory(JSON_FACTORY)
                .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
                .setServiceAccountScopes(CalendarScopes.CALENDAR)
                .setServiceAccountPrivateKeyFromP12File(credsPath))
                .setServiceAccountUser(gsuiteUser)
                .build();
  • OAuth2 para cuentas de servicio