无法使用ServiceAccount模拟从.Net读取Google.Apis.Gmail.V1电子邮件

时间:2018-01-09 17:12:46

标签: c# .net gmail-api google-api-dotnet-client

当我使用Google.Apis.Auth.OAuth2.UserCredential授权时,我的代码效果很好。当我切换到Google.Apis.Auth.OAuth2.ServiceAccountCredential时,相同的代码不起作用。

麻烦可能出现在以下三个地方之一:

  1. 1)。我正确构建ServiceAccountCredential吗?

    2)。我是否正确使用ServiceAccountCredential来访问 用户的帐户?

    3)。 GA管理员是否为服务帐户提供了正确的读取权限 用户的邮件?

  2. 这是无效的代码:

    using Google.Apis.Auth.OAuth2;
    using Google.Apis.Gmail.v1;
    using Google.Apis.Gmail.v1.Data;
    using Google.Apis.Services;
    using Google.Apis.Discovery.v1;
    using Google.Apis.Discovery.v1.Data;
    using Google.Apis.Util.Store;
    string[] asScopes = { GmailService.Scope.GmailModify };
            string msApplicationName = "Gmail API .NET Quickstart";
        string sClientEmail = "blah@blah.gserviceaccount.com" //service account;
            string sUser = "cfo@mydomain.com" //email of the user that I want to read;
            string sPrivateKey = "-----BEGIN PRIVATE KEY-----  blah"//service account private key;
        string[] asScopes = {"https://mail.google.com/"};
    
        //get credential
        ServiceAccountCredential oCred = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(sClientEmail)
        {
            Scopes = asScopes,
            User = sUser //the user to be impersonated
        }.FromPrivateKey(sPrivateKey));
    
        // Create Gmail API service.
            GmailService oSVC  = new GmailService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = oCred,
                ApplicationName = msApplicationName,
             });
    
            // List labels.
            UsersResource.LabelsResource.ListRequest request = oSVC.Users.Labels.List("me");
        IList<Google.Apis.Gmail.v1.Data.Label> labels = request.Execute().Labels;    //<--error here.
         /* --- fails with:
        Google.Apis.Auth.OAuth2.Responses.TokenResponseException was unhandled HResult=-2146233088 Message=Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method.", Uri:"" Source=Google.Apis.Auth
        */
    

    如果有人可以提供有关如何测试ServiceAccountCredential以确定其构造是否正确的示例,以及进一步授权的内容,我真的很感激。

    这些是我的ClientID设置的凭据 enter image description here

    所有这一个令人烦恼的问题是,如果我甚至可以从PrivateKey创建ServiceAccountCredential,因为我看到的所有示例都使用证书,例如:

    var certificate = new X509Certificate2("key2.p12", "notasecret",
                    X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
    
                string userEmail = "abc@gmail.com";
    
                ServiceAccountCredential credential = new ServiceAccountCredential(
                    new ServiceAccountCredential.Initializer(serviceAccountEmail)
                    {
                        Scopes = new string[] { Gmail.v1.GmailService.Scope.GmailReadonly }
                    }.FromCertificate(certificate)
                );
    

1 个答案:

答案 0 :(得分:1)

YAHOO!我有工作了!

所以关键是: 1)。创建服务帐户时必须检查DWD。 2)。为了使应用程序获得我需要使用的范围的确切授权。 因此,您的Google管理员必须为您的应用提供所需的确切范围。

代码如下:

private const string MC_GOOGLE_APP_NAME = "from-google-00110";
private const string MC_GOOGLE_SERVICE_ACCOUNT = "appname@from-google-00110.iam.gserviceaccount.com";
private const string MC_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\xxxx blah blah blah xxx\n-----END PRIVATE KEY-----\n";
private const string MS_GMAIL_QUERY = "label: inbox, label: unread";

//**** CRITICAL! THIS MUST EXACTLY MATCH THE SCOPES THAT YOUR App IS AUTHORZIED FOR ***
private const string MS_SCOPES ="https://www.googleapis.com/auth/gmail.modify"; 
private string msEmailAccount = "mike@blue-mosaic.com"; //the email account you are reading, not mine please

//get credential for service account
ServiceAccountCredential credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(MC_GOOGLE_SERVICE_ACCOUNT)
                {
                    Scopes = MS_SCOPES,
                    User = msEmailAccount 
                }.FromPrivateKey(MC_PRIVATE_KEY));

//create a new gmail service
GmailService oSVC =  GmailService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = MC_GOOGLE_APP_NAME,
            });

List<Google.Apis.Gmail.v1.Data.Message> aoMessageList = new List<Google.Apis.Gmail.v1.Data.Message>();
UsersResource.MessagesResource.ListRequest request = oSVC.Users.Messages.List(msEmailAccount);
request.Q = MS_GMAIL_QUERY;

//keep making requests until all messages are read. 
do
{
    ListMessagesResponse response = request.Execute();
    if (response.Messages != null)
    {
        aoMessageList.AddRange(response.Messages);
        request.PageToken = response.NextPageToken;
    }

} while (!String.IsNullOrEmpty(request.PageToken));

//now read the body of each of the new messages
foreach (Message oMsg in aoMessageList)
{
    string sMsgID = oMsg.Id;

    sState = "Reading Message '" + sMsgID + "'";

    // and this one gets a bit nuts. processing GMAIL messages took me a fair amount
    // of reading, parsing and decoding, so it required a whole class!!
    SaneMessage oThisMsg = new SaneMessage(oSVC, "me", sMsgID);

    //and do something with the message
}

因此,上面的代码不仅显示已登录,还显示正在阅读电子邮件。要阅读Google Apps电子邮件,需要进行大量的解析处理,以处理URL编码的Base64编码消息,这些消息中经常出现奇怪的中断。我写了一个课程来处理所有这一切:

using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Email_Reader_Service
{
    class SaneMessage
    {

        private string msID = "";
        private string msFrom = "";
        private string msDate = "";
        private string msSubject = "";
        private string msBody = "";

        public string ID
        {
            get { return msID; }
        }

        public string From
        {
            get { return msFrom; }
        }

        public DateTime Date
        {
            get { return Convert.ToDateTime(msDate); }
        }

        public string Subject
        {
            get { return msSubject; }
        }

        public string Body
        {
            get { return msBody; }
        }

        public SaneMessage(GmailService service, String userId, String messageId)
        {
            Google.Apis.Gmail.v1.Data.Message oStupidMessage = service.Users.Messages.Get(userId, messageId).Execute();

            string sBackupDate = string.Empty;
            foreach (var mParts in oStupidMessage.Payload.Headers)
            {
                System.Diagnostics.Debug.Print("{0}\t\t\t\t{1}", mParts.Name, mParts.Value);
                switch (mParts.Name)
                {
                    case ("X-Google-Original-Date"):
                        msDate = mParts.Value;
                        break;
                    case ("Date"):
                        sBackupDate = mParts.Value;
                        break;
                    case ("From"):
                        msFrom = mParts.Value;
                        break;
                    case ("Subject"):
                        msSubject = mParts.Value;
                        break;
                }
            }

            //-----------------------------------------------------------------------------------------------
            //the fooking date comes in a plethora of formats.  if the timezone name is appended on the end
            // the datetime conversion can't convert.
            if(msDate.Length == 0)
                msDate = sBackupDate;

            if (msDate.Contains('('))
                msDate= msDate.Substring(0, msDate.LastIndexOf('('));
            //-----------------------------------------------------------------------------------------------

            if (msDate != "" && msFrom != "")
            {
                string sEncodedBody;
                if (oStupidMessage.Payload.Parts == null && oStupidMessage.Payload.Body != null)
                {
                    sEncodedBody = oStupidMessage.Payload.Body.Data;
                }
                else
                {
                    sEncodedBody = getNestedParts(oStupidMessage.Payload.Parts, "");
                }


                ///need to replace some characters as the data for the email's body is base64
                msBody = DecodeURLEncodedBase64EncodedString(sEncodedBody);

            }

        }

        private string getNestedParts(IList<MessagePart> part, string curr)
        {
            string str = curr;
            if (part == null)
            {
                return str;
            }
            else
            {
                foreach (var parts in part)
                {
                    if (parts.Parts == null)
                    {
                        if (parts.Body != null && parts.Body.Data != null)
                        {
                            str += parts.Body.Data;
                        }
                    }
                    else
                    {
                        return getNestedParts(parts.Parts, str);
                    }
                }

                return str;
            }

        }

        /// <summary>
        /// Turn a URL encoded base64 encoded string into readable UTF-8 string.
        /// </summary>
        /// <param name="sInput">base64 URL ENCODED string.</param>
        /// <returns>UTF-8 formatted string</returns>
        private string DecodeURLEncodedBase64EncodedString(string sInput)
        {
            string[] asInput = sInput.Split("=".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

            string sOutput = string.Empty;

            foreach (string sInputPiece in asInput)
            {
                string sBase46codedBody = sInputPiece.Replace("-", "+").Replace("_", "/");  //get rid of URL encoding, and pull any current padding off.
                string sPaddedBase46codedBody = sBase46codedBody.PadRight(sBase46codedBody.Length + (4 - sBase46codedBody.Length % 4) % 4, '=');  //re-pad the string so it is correct length.

                byte[] data = Convert.FromBase64String(sPaddedBase46codedBody);
                sOutput += Encoding.UTF8.GetString(data);
            }

            System.Diagnostics.Debug.Print("{0}\r\n\r\n", sOutput);

            return sOutput;
        }
    }
}