O que há de Novo?
Fórum Outer Space - O maior fórum de games do Brasil

Registre uma conta gratuita hoje para se tornar um membro! Uma vez conectado, você poderá participar neste site adicionando seus próprios tópicos e postagens, além de se conectar com outros membros por meio de sua própria caixa de entrada privada!

  • Anunciando os planos GOLD no Fórum Outer Space
    Visitante, agora você pode ajudar o Fórum Outer Space e receber alguns recursos exclusivos, incluindo navegação sem anúncios e dois temas exclusivos. Veja os detalhes aqui.


Google API + OAuht2 em C#/VB.Net: [RESOLVIDO]

vr-struper

Bam-bam-bam
Mensagens
3.271
Reações
9.012
Pontos
303
Recentemente o Google acabou com o suporte ao acesso usuário/senha do Google API, deixando tudo pelo OAuth2 e isso tem sido um grande problema por aqui. Bom, na verdade não é um grande problema... mas perdi umas funcionalidades bacanas de ter a inserção/exclusão de contatos da conta integrado ao meu sistema.

Basicamente, antes eu tinha isso aqui 100% funcional:
Código:
Private Sub InsereContato()
        Try
            Dim NovoContato As New Contact()
            NovoContato.Name.FullName = Nome        
            InsereEmail(NovoContato)
            InsereFone(NovoContato)
            InsereGrupoPrincipal(NovoContato)
            Dim feedUri As New Uri(ContactsQuery.CreateContactsUri("default"))
            Dim Credenciais As New GDataCredentials(Conta, Senha)
            Dim Settings As New RequestSettings(NomeAplicacao, Credenciais)
            Dim cr As New ContactsRequest(Settings)
            cr.Insert(feedUri, NovoContato)
        Catch ex As Exception
            GravaLogErro(ex.Message, "Google", "Inserir", "erros")
        End Try
End Sub
Mas desde o fim de junho esses GDataCredentials(Conta, Senha) passou a não ser mais aceito, com o servidor retornando erro 404, forçando todos a usarem o acesso com token do OAuth2.
Estou usando uma Service Account, já que é uma aplicação de servidor, e cheguei no seguinte código funcional para o Google Drive, só que ela nãoo se encaixa nas rotinas suportadas pela classe Contacts:
Código:
Private Sub ConectaPlanilhaFuncional(scope As String)
        Dim certificate As New X509Certificate2(Server.MapPath("/certificate/key.p12"), "notasecret", X509KeyStorageFlags.Exportable)
        Dim credential = New ServiceAccountCredential(New ServiceAccountCredential.Initializer(service_email) With { _
             .Scopes = New String() {scope}
        }.FromCertificate(certificate))
        Try
            credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait()
        Catch ex As Exception
        End Try
        Dim requestFactory As New GDataRequestFactory(nome_aplicacao)
        requestFactory.CustomHeaders.Add(String.Format("Authorization: Bearer {0}", credential.Token.AccessToken))
        Dim myService As New SpreadsheetsService(nome_aplicacao)
        myService.RequestFactory = requestFactory
        Dim query As New SpreadsheetQuery()
        Dim feed As SpreadsheetFeed = myService.Query(query)     
        For Each f As SpreadsheetEntry In feed.Entries
            Dim texto As String = f.Title.Text        
        Next     
    End Sub
O código acima acessa todas as planilhas da conta e o laço no final exibe seus nomes, funciona perfeitamente. Com base nisso e alguns outros milhares de posts na internet, cheguei nisso:
Código:
Public Sub InsereContatoGoogle()
        Try
            Dim feedUri As New Uri(ContactsQuery.CreateContactsUri("default"))
            Dim NovoContato As New Contact
            NovoContato.Name.FullName = Nome        
            InsereEmail(NovoContato)
            InsereFone(NovoContato)
            'InserenNoGrupoPrincipal(NovoContato)
            Dim certificate As New X509Certificate2(Server.MapPath("/certificate/key.p12"), "notasecret", X509KeyStorageFlags.Exportable)
            Dim credential = New ServiceAccountCredential(New ServiceAccountCredential.Initializer(service_email) With { _
                 .Scopes = New String() {feedUri.AbsoluteUri}
            }.FromCertificate(certificate))
            Try
                credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait()
            Catch ex As Exception
            End Try
            Dim rs As New RequestSettings(nome_aplicacao, credential.Token.AccessToken) With { _
                                                    .AutoPaging = True, _
                                                    .UseSSL = True _
                                                }
            Dim cr As New ContactsRequest(rs)
            cr.Insert(feedUri, NovoContato)
        Catch ex As Exception
            'GravaLogErro(ex.Message, "Google", "Inserir", "erros")
        End Try
    End Sub
Aí que está o problema: diferente de todas as outras tentativas, esse código não retorna erro 4XX algum (não autorizado), mas também não faz o que se propõe, que é inserir o contato na conta.

Alguém por aqui entende do assunto?
 
Ultima Edição:

moskandre

Bam-bam-bam
Mensagens
9.182
Reações
5.859
Pontos
414
voce ja observou a POST request q esta saindo do seu pc? os header e o que tem no body? E observou a response?
Usa algo mais baixo nivel e printa esses 2 caras, ai vai no site da API do google e ve o que nao ta certo... Talvez essa lib q vc ta usando ainda ta implementando algo da forma errada ou apontando para um endpoint errado.

faco coisas parecidas em python na minha aplicacao e ta tudo ok.
 

vr-struper

Bam-bam-bam
Mensagens
3.271
Reações
9.012
Pontos
303
voce ja observou a POST request q esta saindo do seu pc? os header e o que tem no body? E observou a response?
Usa algo mais baixo nivel e printa esses 2 caras, ai vai no site da API do google e ve o que nao ta certo... Talvez essa lib q vc ta usando ainda ta implementando algo da forma errada ou apontando para um endpoint errado.

faco coisas parecidas em python na minha aplicacao e ta tudo ok.
Obrigado pela ajuda, mas a ideia de usar a API não seria para ela criar os requests/responses com os parâmetros passados e já enviá-los dentro do escopo?

Sendo bem honesto, não tenho certeza de como testar/capturar o request/response. Os headers e body que comentou para analisar é deles?

Eu meio que programo programando, falta conhecimento sobre como interagir com esses mecanismos em baixo nível... :ksnif

OBS: Estou testando em uma aplicação console.
 
Ultima Edição:

moskandre

Bam-bam-bam
Mensagens
9.182
Reações
5.859
Pontos
414
Obrigado pela ajuda, mas a ideia de usar a API não seria para ela criar os requests/responses com os parâmetros passados e já enviá-los dentro do escopo?

Sendo bem honesto, não tenho certeza de como testar/capturar o request/response. Os headers e body que comentou para analisar é deles?

Eu meio que programo programando, falta conhecimento sobre como interagir com esses mecanismos em baixo nível... :ksnif

OBS: Estou testando em uma aplicação console.
Cara nunca mexi com C# mas inspecionar a HTTP request deve ser algo bem basico e deve ter suporte.
Da uma olhada nisso, talvez ajude http://www.telerik.com/fiddler
 

Sephirothrx7

Mil pontos, LOL!
Mensagens
12.406
Reações
2.292
Pontos
1.269
O fiddler é um proxy pros navegadores, não sei se tem como configurar como proxy na aplicação dele. No pior dos casos pode usar um wireshark da vida pra capturar tudo que passa pela placa de rede.

Sent from my Moto G 2014 using Tapatalk
 

vr-struper

Bam-bam-bam
Mensagens
3.271
Reações
9.012
Pontos
303
O fiddler é um proxy pros navegadores, não sei se tem como configurar como proxy na aplicação dele. No pior dos casos pode usar um wireshark da vida pra capturar tudo que passa pela placa de rede.

Sent from my Moto G 2014 using Tapatalk
Cara nunca mexi com C# mas inspecionar a HTTP request deve ser algo bem basico e deve ter suporte.
Da uma olhada nisso, talvez ajude http://www.telerik.com/fiddler
Interessante que já utilizava a página da telerik para ajustar a conversão dos exemplos C# ao meu preferencialmente defasado vb.net.

UPDATE: O código InsereContatoGoogle do primeiro post, que comentei que não retornava erro fazia isso por um motivo: ele estava praticamente funcionando. Por que praticamente?
Cheguei em uma solução para incluir o token com a autorização em uma rotina para visualizar os contatos, vou colocar aqui porque essa inclusão do token na solicitação é bem confusa nos exemplos do Google Contacts (eles basicamente dizem que você deve incluir e partem para as rotinas):
Código:
usingGoogle.Contacts;
usingGoogle.GData.Contacts;
usingGoogle.GData.Client;
usingGoogle.GData.Extensions;
// ...
RequestSettings settings =newRequestSettings("YOUR_APPLICATION_NAME");
// Add authorization token.
// ...
ContactsRequest cr =newContactsRequest(settings);// ...
Código:
Try
    Dim escopo As String = String.Format("https://www.google.com/m8/feeds/contacts/{0}/full/", "default")
    Dim credential As ServiceAccountCredential
    Dim certificate As New X509Certificate2(Server.MapPath("/chave/key.p12"), "notasecret", X509KeyStorageFlags.Exportable)
    credential = New ServiceAccountCredential(New ServiceAccountCredential.Initializer(service_email) With { _
        .Scopes = New String() {escopo} _
    }.FromCertificate(certificate))
    credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait()
    Dim rs = New RequestSettings(nome_aplicacao) With { _
        .OAuth2Parameters = New OAuth2Parameters() With { _
            .AccessToken = credential.Token.AccessToken, .RefreshToken = "600" _
        } _
    }
    Dim cr As New ContactsRequest(rs)
    Dim f As Feed(Of Contact) = cr.GetContacts()
    For x As Integer = 0 To f.Entries.Count - 1
        Response.Write(f.Entries(x).Name.FullName & " | " & f.Entries(x).Emails.Item(0).Address & _
        " | " & f.Entries(x).Phonenumbers.Item(0).Value & " | " & f.Entries(x).Content & " <br/> ")
    Next
Catch ex As Exception
    Response.Write(ex.Message)
End Try
E ela está retornando isso:
2i6l4t1.jpg


Esse é o resultado de todas as vezes em que rodei o código de teste do primeiro post e não dava erro.

Acontece que esses contatos não estão incluídos no Google Contacts da conta onde criei o suporte à API. Por desencargo de consciência, verifiquei inclusive nas outras 2 contas que tenho, caso o fato do navegador estar autenticado em alguma delas tenha afetado em algo (que não deveria e nem ocorreu, mas ainda assim conferi).

Pelo que entendi até então, meu problema está em onde esses registros estão sendo gravados, porque estão sendo gravados e resgatados de algum lugar.

Menos mal, mais confuso... :facepalm
 
Ultima Edição:


vr-struper

Bam-bam-bam
Mensagens
3.271
Reações
9.012
Pontos
303
Pelo que andei vendo inclusive em respostas dos próprios canais de atendimento do Google, escolhi a forma mais complexa de fazer essa conexão. O fato de estar gravando, exibindo e excluindo perfeitamente em uma conta-limbo parece ser justamente pela Service Account exemplo123dfd1fdss@developer.gserviceaccount.com trabalhar como uma conta quase que independente do exemplo@gmail.com, por isso as alterações não são realizadas nos meus contatos e o "impersonate" na Service Account para conta principal necessita bem mais trabalho.

Teoricamente era pra ser a forma mais simples, já que não exige autenticação manual em tela de consentimento mas sim o envio direto de uma chave p12 para fazer a autenticação, mas não é o caso e em todas as respostas (inclusive do suporte do google) à essa dúvida a recomendação que mais encontrei é fazer a autenticação pela tela de consentimento. Ou seja: todo trabalho até aqui vai precisar ser refeito já que tenho que enviar o request com as credenciais para obter o token e pegar o mesmo no response, o que envolve serializar e desserializar jsons e trabalhar com essas requisições.

Você comentou que faz parecido @moskandre, qual metodo usa: service account ou tela de consentimento? A api trata de alguma forma as construção do json encodado para construir/ler as URIs de pedido/retorno dentro do escopo ou você faz isso com código próprio na aplicação?

E pensar que com duas linhas eu tinha isso funcionando...
Código:
Dim Credenciais As New GDataCredentials(Conta, Senha)
Dim Settings As New RequestSettings(NomeAplicacao, Credenciais)
:ksnif:ksnif:ksnif:ksnif:ksnif:ksnif
 
Ultima Edição:

vr-struper

Bam-bam-bam
Mensagens
3.271
Reações
9.012
Pontos
303
Consegui contornar e realmente ficou tudo muito mais simples, sem necessidade de ler chave, Service Account, nem nada mais e também sem necessidade de aparecer a tela de consentimento na minha aplicação.

Só foi necessário acessar a ferramenta OAuth2 Playground do Google e seguir os passos desse video para criar um Refresh Token, armazená-lo (ele não expira) e em seguida usar ele como .Access e .Refresh na requisição para obter o Access Token.

Ficou dessa forma:
Código:
Const client_id = "XXXX.apps.googleusercontent.com"
Const client_secret = "XXXXXXXXXXX"
Const refresh_token = "XXXXXXXXXX"
Const nome_aplicacao = "MinhaAplicacao"
Const user_id = "MeuEmail@gmail.com"

Private Function CriaContactRequest(Optional scope As String = "https://www.google.com/m8/feeds https://www.google.com/m8/feeds/groups/default/full") As ContactsRequest
        Try
            Dim rs = New RequestSettings(nome_aplicacao) With { _
                            .OAuth2Parameters = New OAuth2Parameters() With { _
                            .AccessToken = refresh_token, _
                            .RefreshToken = refresh_token, _
                            .ClientId = client_id, _
                            .ClientSecret = client_secret, _
                            .RedirectUri = redirect_uri, _
                            .Scope = scope _
                        } _
                    }
            Dim cr As New ContactsRequest(rs)
            Return cr
        Catch ex As Exception
            Throw ex
        End Try
    End Function
Obrigado @moskandre e @Sephirothrx7 pela ajuda!

Mas bem que eles poderiam colocar isso na Documentação do Contacts API para facilitar a vida de quem perde tempo tentando adivinhar como alimentar os métodos dela. :kgoogle
 
Ultima Edição:
Topo Fundo