Erro ao criar evento do Google Agenda com uma conta de serviço

Dec 19 2020

Tenho o requisito de criar um evento do Google Agenda em uma agenda e adicionar outros usuários como participantes desse evento. O objetivo é enviar eventos de calendário para os usuários do aplicativo sem seu consentimento (O-Auth).

Depois de ler a documentação do Google, descobri que preciso de uma conta de serviço. Então, criei um projeto e uma conta de serviço de um dos endereços de e-mail do nosso G-Suite, [email protected] e habilitei a API de calendário para o mesmo.

Criei e baixei um par de chaves (JSON) cujo conteúdo é,

{
  "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"
}

E de acordo com a documentação, comecei a escrever o código do fluxo de autenticação,

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;
    }

O credentialobjeto possui a maioria dos detalhes do arquivo-chave. Mas, os campos, serviceAccountUser, accessToken, refreshToken, clientAuthenticatione requestInitializertêm nullvalor. (Estou supondo que algo está errado aqui)

Agora, usando o credentialObject, continuei a escrever o código de acordo com a documentação para criar o 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());

Mas, isso resultou no erro,

{
  "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."
}

Depois de passar um tempo Domain-Wide Delegation, entendi que isso é necessário se tivermos que enviar o evento como outro usuário do nosso g-suite, o que não é necessário para o meu problema. Mas, para depurar, fui em frente e forneci Domain-Wide Delegatione executei novamente o programa. O mesmo erro veio novamente.

Portanto, removi os convidados / participantes do eventobjeto e executei novamente o aplicativo. Desta vez, o programa foi executado sem nenhum erro, mas o link do evento gerado, ao clicar diz Could not find the requested event,.

Não vejo nenhum exemplo de uso da conta de serviço por meio de bibliotecas cliente Java no link do desenvolvedor do Google.

Você pode me informar o que está errado aqui e a documentação oficial / de trabalho sobre como exatamente criar um evento do Google Agenda a partir do meu projeto, adicionar outros (não g Suite também) usuários para adicionar como participantes, para que eu não tenha obter o consentimento de outros usuários para adicionar eventos às suas próprias agendas?

Obrigado.

Respostas

DaImTo Dec 21 2020 at 15:06

Eu quero dizer que isso é algo novo, se você está vendo muitas perguntas e tutoriais que não afirmam que você precisa fazer isso, é porque as contas de serviço costumavam ser capazes de enviar convites, isso é algo bloqueado pelo Google cerca de um ano atrás.

Isso deve funcionar, mas não testei porque não tenho mais acesso a uma conta Gsuite. Você precisa que o administrador do gsuite configure a delegação de todo o domínio para outro usuário. Em seguida, a conta de serviço precisa representar esse usuário para que apareça como o usuário que está enviando os convites.

O único exemplo que tenho de como isso é adicionado é em .net

exemplo .net

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

            }.FromCertificate(certificate);

Suposição de exemplo de Java

Não sou um desenvolvedor Java, mas a biblioteca cliente .net e a biblioteca cliente Java são muito semelhantes na forma como foram desenvolvidas. Eu acho que você está procurando um método chamado 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 contas de serviço