I have code that works well when I authorize with a Google.Apis.Auth.OAuth2.UserCredential. The same code does not work when I switch to a Google.Apis.Auth.OAuth2.ServiceAccountCredential.
The trouble could be in one of three places:
1). Am I constructing the ServiceAccountCredential correctly?
2). Am I using the ServiceAccountCredential correctly to access the
user's account?
3). Did the GA Admin give the service account proper access to read
user's mail?
This is the code that is not working:
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
*/
If anyone could help with examples of how to test a ServiceAccountCredential to see if it is constructed correctly, and further, what it has been authorized to, I'd really appreciate it.
These are the credentials set for my ClientID
A nagging question in all this is if I can even create a ServiceAccountCredential from a PrivateKey, as all the examples I have seen use a Certificate, eg:
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)
);
YAHOO! I GOT IT WORKING!!
So the key was:
1). DWD must be checked when creating service account.
2). To have the application authorized for EXACTLY the scopes I needed to use. So your Google Admin has got to give your app exactly the scopes you need.
Here is the code:
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
}
So the code above not only shows getting logged in, it also shows reading email. For reading Google Apps email, it required a bunch of parsing to deal with URL encoded Base64 encoded messages that often had weird breaks in them. I wrote a class to handle all this:
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;
}
}
}
Related
From Cloud pub/sub push service i got a history id. Using that history id i am trying to read the recent mail's but It returns null.
I have configured cloud pub/sub push subscription and add a watch to "Unread" label.
Scenario 1:
I have received a push notification. From that push notification i have taken history id to get the recent messages. it's returning me null value.
Scenario 2:
I have logged into that configured mail id and then the message loaded in inbox. After that if i try to read i am getting the history list.
static string[] Scopes = { GmailService.Scope.MailGoogleCom };
static void Main(string[] args)
{
string UserId = "####.gmail.com";
UserCredential credential;
using (var stream =
new FileStream("client_secret_#####.json", FileMode.Open, FileAccess.Read))
{
string credPath = "token.json";
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
UserId,
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
List<History> result = new List<History>();
UsersResource.HistoryResource.ListRequest request = service.Users.History.List(UserId);
//history id received from cloud pub/sub push subscription.
request.StartHistoryId = Convert.ToUInt64("269871");
do
{
try
{
ListHistoryResponse response = request.Execute();
if (response.History != null)
{
result.AddRange(response.History);
}
request.PageToken = response.NextPageToken;
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
} while (!String.IsNullOrEmpty(request.PageToken));
foreach (var vHistory in result)
{
foreach (var vMsg in vHistory.Messages)
{
string date = string.Empty;
string from = string.Empty;
string subject = string.Empty;
string body = string.Empty;
var emailInfoRequest = service.Users.Messages.Get(UserId, vMsg.Id);
var emailInfoResponse = emailInfoRequest.Execute();
if(emailInfoResponse!= null)
{
foreach (var mParts in emailInfoResponse.Payload.Headers)
{
if (mParts.Name == "Date")
{
date = mParts.Value;
}
else if (mParts.Name == "From")
{
from = mParts.Value;
}
else if (mParts.Name == "Subject")
{
subject = mParts.Value;
}
if (date != "" && from != "")
{
if (emailInfoResponse.Payload.Parts != null)
{
foreach (MessagePart p in emailInfoResponse.Payload.Parts)
{
if (p.MimeType == "text/html")
{
byte[] data = FromBase64ForUrlString(p.Body.Data);
body = Encoding.UTF8.GetString(data);
}
else if(p.Filename!=null && p.Filename.Length>0)
{
string attId = p.Body.AttachmentId;
string outputDir = #"D:\#####\";
MessagePartBody attachPart = service.Users.Messages.Attachments.Get(UserId, vMsg.Id, attId).Execute();
String attachData = attachPart.Data.Replace('-', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
File.WriteAllBytes(Path.Combine(outputDir, p.Filename), data);
}
}
}
else
{
byte[] data = FromBase64ForUrlString(emailInfoResponse.Payload.Body.Data);
body = Encoding.UTF8.GetString(data);
}
}
}
}
}
}
public static byte[] FromBase64ForUrlString(string base64ForUrlInput)
{
int padChars = (base64ForUrlInput.Length % 4) == 0 ? 0 : (4 - (base64ForUrlInput.Length % 4));
StringBuilder result = new StringBuilder(base64ForUrlInput, base64ForUrlInput.Length + padChars);
result.Append(String.Empty.PadRight(padChars, '='));
result.Replace('-', '+');
result.Replace('_', '/');
return Convert.FromBase64String(result.ToString());
}
}
Please let me know how to read the full message using history id. when i receive push notification.
The Gmail Api documentation states that the Users.history:list method requires startHistoryId as a parameter to be executed, rather than giving you this parameter as a response. This is confusing, since it states as an optional parameter, but it is also specifies that it is required. The documentation also specifies:
The supplied startHistoryId should be obtained from the historyId of a
message, thread, or previous list response.
I suggest you to test the methods you use first with "Try this API" and OAuth 2.0 Playground. This makes it easier to understand which parameters you need to supply and which responses you can obtain from each method.
I have dealt with this. The point is that the history_id you are receiving is to be interpreted like the "latest moment when something happened". So, in order to make this work, you MUST use a history_id coming from a previous execution (that, don't forget, in GMail Push API means that you have to implement the initial full sync, or at the very least you should be executing a second run of your partial sync), which will return the events that span from the previous history_id to the one you just received.
I have just published an article on medium, since the detail of the history_id, in my opinion, can be a little sneaky. Article is here.
What I want to achieve:
I'm using the Gmail API and basically I would like to connect to my GMail account to read my emails, of INBOX category, and get basic info for each message (title/subject, from, to, date, and the sender).
Problems:
I'm trying to adapt this Google sample, written in C#, to my own needs, I'm searching for a solution in C# or Vb.Net, no matter.
(Be aware that Google shows different code examples for different user-countries, so the code of that webpage maybe will not be the same for every one, that Google's logic really sucks.)
The problems I have with the code below, are these:
I'm getting an empty value in lblInbox.MessagesTotal property.
msgItem.Raw property is always empty too.
I haven't yet discovered how to parse only the messages that are inside the INBOX category.
I haven't yet discovered how to determine if a message is read or unread.
I haven't yet discovered how to determine the basic info of a message (subject, from, to, date, sender).
This is what I've tried, note that when adapting the Google's sample, I assumed that "user" argument should be the Gmail user account name ("MyEmail#GMail.com"), but I'm not sure it should be that.
Imports System.Collections.Generic
Imports System.IO
Imports System.Linq
Imports System.Text
Imports System.Threading
Imports System.Threading.Tasks
Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Services
Imports Google.Apis.Util.Store
Imports Google.Apis.Gmail
Imports Google.Apis.Gmail.v1
Imports Google.Apis.Gmail.v1.Data
Imports Google.Apis.Gmail.v1.UsersResource
Public Class Form1 : Inherits Form
Private Async Sub Test() Handles MyBase.Shown
Await GmailTest()
End Sub
Public Async Function GmailTest() As Task
Dim credential As UserCredential
Using stream As New FileStream("C:\GoogleAPIKey.json", FileMode.Open, FileAccess.Read)
credential = Await GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets,
{GmailService.Scope.MailGoogleCom},
"MyEmail#GMail.com",
CancellationToken.None)
End Using
' Create the service.
Dim service As New GmailService(New BaseClientService.Initializer() With {
.HttpClientInitializer = credential,
.ApplicationName = "What I need to put here?"
})
' Get the "INBOX" label/category.
Dim lblReq As UsersResource.LabelsResource.ListRequest = service.Users.Labels.List("me")
Dim lblInbox As Data.Label = lblReq.Execute().Labels.Where(Function(lbl) lbl.Name = "INBOX").Single
Dim msgCount As Integer? = lblInbox.MessagesTotal
MsgBox("Messages Count: " & msgCount)
If (msgCount <> 0) Then
' Define message parameters of request.
Dim msgReq As UsersResource.MessagesResource.ListRequest = service.Users.Messages.List("me")
' List messages of INBOX category.
Dim messages As IList(Of Data.Message) = msgReq.Execute().Messages
Console.WriteLine("Messages:")
If (messages IsNot Nothing) AndAlso (messages.Count > 0) Then
For Each msgItem As Data.Message In messages
MsgBox(msgItem.Raw)
Next
End If
End If
End Function
End Class
Question:
I will ask for the most important need (however, any help to solve the other mentioned problems are very welcome):
In C# or VB.Net, how can I obtain a collection to iterate all the emails that are in the INBOX group?.
Update:
This is the code that I'm using right now, the intention is to retrieve a collection of all Messages of the specified mailbox label, the problem is that the Payload and Body member of newMsg object is null, so I can't read the email.
What I'm doing wrong?.
Public Async Function GetMessages(ByVal folder As Global.Google.Apis.Gmail.v1.Data.Label) As Task(Of List(Of Global.Google.Apis.Gmail.v1.Data.Message))
If Not (Me.isAuthorizedB) Then
Throw New InvalidOperationException(Me.authExceptionMessage)
Else
Dim msgsRequest As UsersResource.MessagesResource.ListRequest = Me.client.Users.Messages.List("me")
With msgsRequest
.LabelIds = New Repeatable(Of String)({folder.Id})
.MaxResults = 50
'.Key = "YOUR API KEY"
End With
Dim msgsResponse As ListMessagesResponse = Await msgsRequest.ExecuteAsync()
Dim messages As New List(Of Global.Google.Apis.Gmail.v1.Data.Message)
Do While True
For Each msg As Global.Google.Apis.Gmail.v1.Data.Message In msgsResponse.Messages
Dim msgRequest As UsersResource.MessagesResource.GetRequest = Me.client.Users.Messages.Get("me", msg.Id)
msgRequest.Format = MessagesResource.GetRequest.FormatEnum.Full
Dim newMsg As Message = Await msgRequest.ExecuteAsync()
messages.Add(newMsg)
Next msg
If Not String.IsNullOrEmpty(msgsResponse.NextPageToken) Then
msgsRequest.PageToken = msgsResponse.NextPageToken
msgsResponse = Await msgsRequest.ExecuteAsync()
Else
Exit Do
End If
Loop
Return messages
End If
End Function
Currently for some reason or another many of the properties are coming back null from any of the requests. We can still get around that if we have a list of the email ids. We then can use these email ids to send out another request to retrieve further details: from, date, subject, and body. #DalmTo was on the right track as well, but not close enough about the headers as it has changed recently which will require a few more requests.
private async Task getEmails()
{
try
{
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows for read-only access to the authenticated
// user's account, but not other types of account access.
new[] { GmailService.Scope.GmailReadonly, GmailService.Scope.MailGoogleCom, GmailService.Scope.GmailModify },
"NAME OF ACCOUNT NOT EMAIL ADDRESS",
CancellationToken.None,
new FileDataStore(this.GetType().ToString())
);
}
var gmailService = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
var emailListRequest = gmailService.Users.Messages.List("EMAILADDRESSHERE");
emailListRequest.LabelIds = "INBOX";
emailListRequest.IncludeSpamTrash = false;
//emailListRequest.Q = "is:unread"; // This was added because I only wanted unread emails...
// Get our emails
var emailListResponse = await emailListRequest.ExecuteAsync();
if (emailListResponse != null && emailListResponse.Messages != null)
{
// Loop through each email and get what fields you want...
foreach (var email in emailListResponse.Messages)
{
var emailInfoRequest = gmailService.Users.Messages.Get("EMAIL ADDRESS HERE", email.Id);
// Make another request for that email id...
var emailInfoResponse = await emailInfoRequest.ExecuteAsync();
if (emailInfoResponse != null)
{
String from = "";
String date = "";
String subject = "";
String body = "";
// Loop through the headers and get the fields we need...
foreach (var mParts in emailInfoResponse.Payload.Headers)
{
if (mParts.Name == "Date")
{
date = mParts.Value;
}
else if(mParts.Name == "From" )
{
from = mParts.Value;
}
else if (mParts.Name == "Subject")
{
subject = mParts.Value;
}
if (date != "" && from != "")
{
if (emailInfoResponse.Payload.Parts == null && emailInfoResponse.Payload.Body != null)
{
body = emailInfoResponse.Payload.Body.Data;
}
else
{
body = getNestedParts(emailInfoResponse.Payload.Parts, "");
}
// Need to replace some characters as the data for the email's body is base64
String codedBody = body.Replace("-", "+");
codedBody = codedBody.Replace("_", "/");
byte[] data = Convert.FromBase64String(codedBody);
body = Encoding.UTF8.GetString(data);
// Now you have the data you want...
}
}
}
}
}
}
catch (Exception)
{
MessageBox.Show("Failed to get messages!", "Failed Messages!", MessageBoxButtons.OK);
}
}
static 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;
}
}
Currently, this method will retrieve all email ids and for each email id get the subject,from, date and body of each email. There are comments throughout the method. If there is something you do not understand, please let me know. On another note: this was tested again before posting this as an answer.
sorry this is not an answer, i can't add a comment to Zaggler's answer(just joined), so just post as a new answer, Zaggler's answer is very good, but there is a small problem. when the email body has more then one part. the Convert.FromBase64..... doesn't work on two joined base64 strings. so an exception will be occure. better convert then joined the body parts.
some one ask for the code, and here is the completed tested code. most of them are copied from Zaggler, but i end up with some exceptions. so i traced down to the problem described above.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
namespace GmailTests
{
class Program
{
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/gmail-dotnet-quickstart.json
static string[] Scopes = { GmailService.Scope.GmailModify };
static string ApplicationName = "Gmail API .NET Quickstart";
static void Main(string[] args)
{
UserCredential credential;
using (var stream =
new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
{
string credPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/gmail-dotnet-quickstart2.json");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}
// Create Gmail API service.
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
var re = service.Users.Messages.List("me");
re.LabelIds = "INBOX";
re.Q = "is:unread"; //only get unread;
var res = re.Execute();
if (res != null && res.Messages != null)
{
Console.WriteLine("there are {0} emails. press any key to continue!", res.Messages.Count);
Console.ReadKey();
foreach (var email in res.Messages)
{
var emailInfoReq = service.Users.Messages.Get("me", email.Id);
var emailInfoResponse = emailInfoReq.Execute();
if (emailInfoResponse != null)
{
String from = "";
String date = "";
String subject = "";
String body = "";
//loop through the headers and get the fields we need...
foreach (var mParts in emailInfoResponse.Payload.Headers)
{
if (mParts.Name == "Date")
{
date = mParts.Value;
}
else if (mParts.Name == "From")
{
from = mParts.Value;
}
else if (mParts.Name == "Subject")
{
subject = mParts.Value;
}
if (date != "" && from != "")
{
if (emailInfoResponse.Payload.Parts == null && emailInfoResponse.Payload.Body != null)
body = DecodeBase64String(emailInfoResponse.Payload.Body.Data);
else
body = GetNestedBodyParts(emailInfoResponse.Payload.Parts, "");
//now you have the data you want....
}
}
//Console.Write(body);
Console.WriteLine("{0} -- {1} -- {2}", subject, date, email.Id);
Console.ReadKey();
}
}
}
}
static String DecodeBase64String(string s)
{
var ts = s.Replace("-", "+");
ts = ts.Replace("_", "/");
var bc = Convert.FromBase64String(ts);
var tts = Encoding.UTF8.GetString(bc);
return tts;
}
static String GetNestedBodyParts(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)
{
var ts = DecodeBase64String(parts.Body.Data);
str += ts;
}
}
else
{
return GetNestedBodyParts(parts.Parts, str);
}
}
return str;
}
}
}
}
First: up-vote #codexer 's answer.
Second, use the following function in his code to decode the base64URL encoded body. Google not only base64 encoded the body, it is also URL encoded :-/
/// <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 sBase46codedBody = sInput.Replace("-", "+").Replace("_", "/").Replace("=", String.Empty); //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);
return Encoding.UTF8.GetString(data);
}
The user parameter in GoogleWebAuthorizationBroker.AuthorizeAsync is just used by FileDatastore to store your credentials check my tutorial Google .net – FileDatastore demystified for more information.
My VB.net is very rusty like 6 years rusty but in C# you could do something like this
UsersResource.MessagesResource.ListRequest request = service.Users.Messages.List("Users email address");
var response = request.Execute();
foreach (var item in response.Messages) {
Console.WriteLine(item.Payload.Headers);
}
MessageResource.ListRequest returns a list of message objects you can loop though them.
Users.Messages contains header which should have the subject and the to and from.
I also have a really old C# tutorial on gmail that might help.
Update to answer your update:
What happens when you remove:
.LabelIds = New Repeatable(Of String)({folder.Id})
labelIds string Only return messages with labels that match all of the specified label IDs.
It appears you are sending a folder id. try using user.lables.list which returns Lists all labels in the user's mailbox
UsersResource.MessagesResource.GetRequest getReq = null;
Google.Apis.Gmail.v1.Data.Message msg = null;
getReq = gmailServiceObj.Users.Messages.Get(userEmail, MessageID);
getReq.Format = UsersResource.MessagesResource.GetRequest.FormatEnum.Raw;
msg = getReq.Execute();
string converted = msg.Raw.Replace('-', '+');
converted = converted.Replace('_', '/');
byte[] decodedByte = Convert.FromBase64String(converted);
converted = null;
f_Path = Path.Combine(m_CloudParmsObj.m_strDestinationPath,MessageID + ".eml");
if (!Directory.Exists(m_CloudParmsObj.m_strDestinationPath))
Directory.CreateDirectory(m_CloudParmsObj.m_strDestinationPath);
// Create eml file
File.WriteAllBytes(f_Path, decodedByte);
We can get the .eml file with all message properties like this.
codedBody = codedBody.Replace(" ", "+");
codedBody = codedBody.Replace("=", "+");
to parse the body part I recommend adding these lines of code as well
I am using the below code to create a instance in the hp cloud(or any openstack). I am having issues with determining the base url.There could be other errors and i would appreciate anybody seeing them also. So do i find out the base url. I had a look through the hp docs but to no avail ?! Also i am unsure of how to obtain the image id, i presume the flavor is 'small' etc?
using System;
using System.Web;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using net.openstack.Core.Domain;
using net.openstack.Providers.Rackspace;
namespace Openstack2
{
class Program
{
static void Main(string[] args)
{
Uri baseUrl = new Uri("https://horizon.hpcloud.com/auth/login/");
CloudIdentity cloudId = new CloudIdentity()
{
Username = "#####",
Password = "####"
};
CloudIdentityProvider cip = new CloudIdentityProvider(cloudId, baseUrl);
UserAccess ua = cip.Authenticate(cloudId);
CloudServersProvider provider = new CloudServersProvider(cloudId);
Metadata metaData = new Metadata(); // Add some metadata just because we can
metaData.Add("Description", "Example 4 - Getting Started");
string serverName = "Server a14";
string imageId = "###";
string flavorId = "standard.xsmall";
NewServer newServer = provider.CreateServer(serverName, imageId, flavorId,DiskConfiguration.Manual, metaData);
}
}
}
The above code is based on the rackspace sdk to connect to the hp cloud, so that could be an issue. But i also used the following code based on the other .net openstack api.:
var identityUrl = "https://horizon.hpcloud.com/auth/login/";
var imageUrl = "http://server:9292";
var username = "####";
var password = "###";
var cloudId = new CloudIdentity() { Username = username, Password = password };
var cloudIdProvider = new CloudIdentityProvider(new Uri(identityUrl));
cloudIdProvider.Authenticate(cloudId);
var cloudServersProvider = new CloudServersProvider(cloudId, cloudIdProvider);
var newServer = cloudServersProvider.CreateServer("Team 101 Server a14", "Team 101 Server a14", "standard.xsmall");
Still will not connect to my hp openstack. I think i will half to ditch c# and maybe go with powershell or nova.
i am using HP Cloud and this is how i get the baseUrl:
...
using HPCloud.Common;
using HPCloud.Objects;
using HPCloud.Objects.Utility;
using HPCloud.Objects.DataAccess;
using HPCloud.Objects.Domain;
using HPCloud.Objects.Domain.Compute;
using HPCloud.Objects.Domain.Admin;
session = Session.CreateSession("accessKey", "secretKey", "tennantID");
private Session session = null;
public static string GenerateUrl(Session session, string bucket_name, string key)
{
string baseUrl = session.Context.ServiceCatalog.GetService(HPCloud.Objects.Domain.Admin.Services.ObjectStorage).Url;
return baseUrl + "/" + bucket_name + "/" + key;
}
You will need to get accesskey, secretkey, and tennantID from your cloud admin page.
You may need to add HPCloud-API and BouncyCastle from Nuget.
so now you can use the following to drop a file in your cloud bucket:
public static bool PutFile(Session session, string bucket_name, string file_path, out string key)
{
if (!File.Exists(file_path))
{
throw new FileNotFoundException(file_path);
}
bool success = false;
key = System.IO.Path.GetFileName(file_path);
try
{
var soRepo = session.Factory.CreateStorageObjectRepository();
string fullUrl = GenerateUrl(session, bucket_name, key);
soRepo.Copy(file_path, fullUrl, false);
success = true;
}
catch
{
success = false;
key = string.Empty;
}
return success;
}
I have been looking for a good solution all day, but Google evolves so fast that I can't find something working. What I want to do is that, I have a Web app that has an admin section where user need to be logged in to see the information. In this section I want to show some data from GA, like pageviews for some specific urls. Since it's not the user information that I'm showing but the google analytics'user I want to connect passing information (username/password or APIKey) but I can't find out how. All the sample I found use OAuth2 (witch, if I understand, will ask the visitor to log in using google).
What I found so far :
Google official Client Library for .Net : http://code.google.com/p/google-api-dotnet-client/, no sample for GA
official developers help : https://developers.google.com/analytics/
an other question with code on SO : Google Analytics API - Programmatically fetch page views in server side but I get a 403 when I try to authenticate
some source that access the API : http://www.reimers.dk/jacob-reimers-blog/added-google-analytics-reader-for-net downloaded the source but I can't figure out how it works
this other question on SO : Google Analytics Access with C# but it does not help
while writing this they suggest me this 09 old post Google Analytics API and .Net.
Maybe I'm just tired and that tomorrow it will be easy to find a solution but right now I need help!
Thanks
It requires a bit of setup on the google side but it's actually quite simple. I will list step by step.
First you will need to create an application in the Google cloud console and enable the Analytics API.
Go to http://code.google.com/apis/console
Select the drop down and create a project if you do not already have one
Once the project is created click on services
From here enable the Analytics API
Now that the Analytics API is enabled the next step will be to enable a service account to access your desired analytics profiles/sites. The service account will allow you to log in without having to prompt a user for credentials.
Go to http://code.google.com/apis/console and choose the project you
created from the drop down.
Next go to the "API Access" section and click the "Create another client id" button.
In the Create Client ID window choose service account and click
create client id.
Download the public key for this account if it doesn't start the
download automatically.You will need this later on when you code for
authorization.
Before exiting copy the service accounts auto generated email address as you will need this in the next step. The client email looks like #developer.gserviceaccount.com
Now that we have a service account you will need to allow this service account to access to your profiles/sites in Google Analytics.
Log into Google Analytics.
Once logged in click on the Admin button to the bottem left on the
screen.
In Admin click the account drop down and select the account/site you would like your service account to be able to access then click on "User Management" under the account section.
Enter the email address that was generated for your service account and give it read and analyze permission.
Repeat these steps for any other account/site you would like your service to have access to.
Now that the setup is done for the service account to access Google Analytics through the API we can start to code.
Get this package from NuGet:
Google.Apis.Analytics.v3 Client Library
Add these usings:
using Google.Apis.Analytics.v3;
using Google.Apis.Analytics.v3.Data;
using Google.Apis.Services;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Auth.OAuth2;
using System.Collections.Generic;
using System.Linq;
Some things to note are.
The keyPath is the path to the key file you downloaded with a .p12 file extention.
The accountEmailAddress is the api email we got earlier.
Scope is an Enum in the Google.Apis.Analytics.v3.AnalyticService class that dictates the url to use in order to authorize (ex: AnalyticsService.Scope.AnalyticsReadonly ).
Application name is a name of your choosing that tells the google api what is accessing it (aka: it can be what ever you choose).
Then the code to do some basic calls is as follows.
public class GoogleAnalyticsAPI
{
public AnalyticsService Service { get; set; }
public GoogleAnalyticsAPI(string keyPath, string accountEmailAddress)
{
var certificate = new X509Certificate2(keyPath, "notasecret", X509KeyStorageFlags.Exportable);
var credentials = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(accountEmailAddress)
{
Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly }
}.FromCertificate(certificate));
Service = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = "WorthlessVariable"
});
}
public AnalyticDataPoint GetAnalyticsData(string profileId, string[] dimensions, string[] metrics, DateTime startDate, DateTime endDate)
{
AnalyticDataPoint data = new AnalyticDataPoint();
if (!profileId.Contains("ga:"))
profileId = string.Format("ga:{0}", profileId);
//Make initial call to service.
//Then check if a next link exists in the response,
//if so parse and call again using start index param.
GaData response = null;
do
{
int startIndex = 1;
if (response != null && !string.IsNullOrEmpty(response.NextLink))
{
Uri uri = new Uri(response.NextLink);
var paramerters = uri.Query.Split('&');
string s = paramerters.First(i => i.Contains("start-index")).Split('=')[1];
startIndex = int.Parse(s);
}
var request = BuildAnalyticRequest(profileId, dimensions, metrics, startDate, endDate, startIndex);
response = request.Execute();
data.ColumnHeaders = response.ColumnHeaders;
data.Rows.AddRange(response.Rows);
} while (!string.IsNullOrEmpty(response.NextLink));
return data;
}
private DataResource.GaResource.GetRequest BuildAnalyticRequest(string profileId, string[] dimensions, string[] metrics,
DateTime startDate, DateTime endDate, int startIndex)
{
DataResource.GaResource.GetRequest request = Service.Data.Ga.Get(profileId, startDate.ToString("yyyy-MM-dd"),
endDate.ToString("yyyy-MM-dd"), string.Join(",", metrics));
request.Dimensions = string.Join(",", dimensions);
request.StartIndex = startIndex;
return request;
}
public IList<Profile> GetAvailableProfiles()
{
var response = Service.Management.Profiles.List("~all", "~all").Execute();
return response.Items;
}
public class AnalyticDataPoint
{
public AnalyticDataPoint()
{
Rows = new List<IList<string>>();
}
public IList<GaData.ColumnHeadersData> ColumnHeaders { get; set; }
public List<IList<string>> Rows { get; set; }
}
}
Other Links that will prove helpful:
Analytic API Explorer - Query API From The Web
Analytic API Explorer version 2 - Query API From The Web
Dimensions and Metrics Reference
Hopefully this helps someone trying to do this in the future.
I did a lot of search and finally either looking up code from multiple places and then wrapping my own interface around it i came up with the following solution. Not sure if people paste their whole code here, but i guess why not save everyone else time :)
Pre-requisites, you will need to install Google.GData.Client and google.gdata.analytics package/dll.
This is the main class that does the work.
namespace Utilities.Google
{
public class Analytics
{
private readonly String ClientUserName;
private readonly String ClientPassword;
private readonly String TableID;
private AnalyticsService analyticsService;
public Analytics(string user, string password, string table)
{
this.ClientUserName = user;
this.ClientPassword = password;
this.TableID = table;
// Configure GA API.
analyticsService = new AnalyticsService("gaExportAPI_acctSample_v2.0");
// Client Login Authorization.
analyticsService.setUserCredentials(ClientUserName, ClientPassword);
}
/// <summary>
/// Get the page views for a particular page path
/// </summary>
/// <param name="pagePath"></param>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <param name="isPathAbsolute">make this false if the pagePath is a regular expression</param>
/// <returns></returns>
public int GetPageViewsForPagePath(string pagePath, DateTime startDate, DateTime endDate, bool isPathAbsolute = true)
{
int output = 0;
// GA Data Feed query uri.
String baseUrl = "https://www.google.com/analytics/feeds/data";
DataQuery query = new DataQuery(baseUrl);
query.Ids = TableID;
//query.Dimensions = "ga:source,ga:medium";
query.Metrics = "ga:pageviews";
//query.Segment = "gaid::-11";
var filterPrefix = isPathAbsolute ? "ga:pagepath==" : "ga:pagepath=~";
query.Filters = filterPrefix + pagePath;
//query.Sort = "-ga:visits";
//query.NumberToRetrieve = 5;
query.GAStartDate = startDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
query.GAEndDate = endDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
Uri url = query.Uri;
DataFeed feed = analyticsService.Query(query);
output = Int32.Parse(feed.Aggregates.Metrics[0].Value);
return output;
}
public Dictionary<string, int> PageViewCounts(string pagePathRegEx, DateTime startDate, DateTime endDate)
{
// GA Data Feed query uri.
String baseUrl = "https://www.google.com/analytics/feeds/data";
DataQuery query = new DataQuery(baseUrl);
query.Ids = TableID;
query.Dimensions = "ga:pagePath";
query.Metrics = "ga:pageviews";
//query.Segment = "gaid::-11";
var filterPrefix = "ga:pagepath=~";
query.Filters = filterPrefix + pagePathRegEx;
//query.Sort = "-ga:visits";
//query.NumberToRetrieve = 5;
query.GAStartDate = startDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
query.GAEndDate = endDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
Uri url = query.Uri;
DataFeed feed = analyticsService.Query(query);
var returnDictionary = new Dictionary<string, int>();
foreach (var entry in feed.Entries)
returnDictionary.Add(((DataEntry)entry).Dimensions[0].Value, Int32.Parse(((DataEntry)entry).Metrics[0].Value));
return returnDictionary;
}
}
}
And this is the interface and implementation that i use to wrap it up with.
namespace Utilities
{
public interface IPageViewCounter
{
int GetPageViewCount(string relativeUrl, DateTime startDate, DateTime endDate, bool isPathAbsolute = true);
Dictionary<string, int> PageViewCounts(string pagePathRegEx, DateTime startDate, DateTime endDate);
}
public class GooglePageViewCounter : IPageViewCounter
{
private string GoogleUserName
{
get
{
return ConfigurationManager.AppSettings["googleUserName"];
}
}
private string GooglePassword
{
get
{
return ConfigurationManager.AppSettings["googlePassword"];
}
}
private string GoogleAnalyticsTableName
{
get
{
return ConfigurationManager.AppSettings["googleAnalyticsTableName"];
}
}
private Analytics analytics;
public GooglePageViewCounter()
{
analytics = new Analytics(GoogleUserName, GooglePassword, GoogleAnalyticsTableName);
}
#region IPageViewCounter Members
public int GetPageViewCount(string relativeUrl, DateTime startDate, DateTime endDate, bool isPathAbsolute = true)
{
int output = 0;
try
{
output = analytics.GetPageViewsForPagePath(relativeUrl, startDate, endDate, isPathAbsolute);
}
catch (Exception ex)
{
Logger.Error(ex);
}
return output;
}
public Dictionary<string, int> PageViewCounts(string pagePathRegEx, DateTime startDate, DateTime endDate)
{
var input = analytics.PageViewCounts(pagePathRegEx, startDate, endDate);
var output = new Dictionary<string, int>();
foreach (var item in input)
{
if (item.Key.Contains('&'))
{
string[] key = item.Key.Split(new char[] { '?', '&' });
string newKey = key[0] + "?" + key.FirstOrDefault(k => k.StartsWith("p="));
if (output.ContainsKey(newKey))
output[newKey] += item.Value;
else
output[newKey] = item.Value;
}
else
output.Add(item.Key, item.Value);
}
return output;
}
#endregion
}
}
And now the rest is the obvious stuff - you will have to add the web.config values to your application config or webconfig and call IPageViewCounter.GetPageViewCount
This answer is for those of you who want access to your own Analytics account and want to use the new Analytics Reporting API v4.
I recently wrote a blog post about how to get Google Analytics data using C#. Read there for all the details.
You first need to choose between connecting with OAuth2 or a Service Account. I'll assume you own the Analytics account, so you need to create a "Service account key" from the Google APIs Credentials page.
Once you create that, download the JSON file and put it in your project (I put mine in my App_Data folder).
Next, install the Google.Apis.AnalyticsReporting.v4 Nuget package. Also install Newtonsoft's Json.NET.
Include this class somewhere in your project:
public class PersonalServiceAccountCred
{
public string type { get; set; }
public string project_id { get; set; }
public string private_key_id { get; set; }
public string private_key { get; set; }
public string client_email { get; set; }
public string client_id { get; set; }
public string auth_uri { get; set; }
public string token_uri { get; set; }
public string auth_provider_x509_cert_url { get; set; }
public string client_x509_cert_url { get; set; }
}
And here's what you've been waiting for: a full example!
string keyFilePath = Server.MapPath("~/App_Data/Your-API-Key-Filename.json");
string json = System.IO.File.ReadAllText(keyFilePath);
var cr = JsonConvert.DeserializeObject(json);
var xCred = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(cr.client_email)
{
Scopes = new[] {
AnalyticsReportingService.Scope.Analytics
}
}.FromPrivateKey(cr.private_key));
using (var svc = new AnalyticsReportingService(
new BaseClientService.Initializer
{
HttpClientInitializer = xCred,
ApplicationName = "[Your Application Name]"
})
)
{
// Create the DateRange object.
DateRange dateRange = new DateRange() { StartDate = "2017-05-01", EndDate = "2017-05-31" };
// Create the Metrics object.
Metric sessions = new Metric { Expression = "ga:sessions", Alias = "Sessions" };
//Create the Dimensions object.
Dimension browser = new Dimension { Name = "ga:browser" };
// Create the ReportRequest object.
ReportRequest reportRequest = new ReportRequest
{
ViewId = "[A ViewId in your account]",
DateRanges = new List() { dateRange },
Dimensions = new List() { browser },
Metrics = new List() { sessions }
};
List requests = new List();
requests.Add(reportRequest);
// Create the GetReportsRequest object.
GetReportsRequest getReport = new GetReportsRequest() { ReportRequests = requests };
// Call the batchGet method.
GetReportsResponse response = svc.Reports.BatchGet(getReport).Execute();
}
We start by deserializing the service account key information from the JSON file and convert it to a PersonalServiceAccountCred object. Then, we create the ServiceAccountCredential and connect to Google via the AnalyticsReportingService. Using that service, we then prepare some basic filters to pass to the API and send off the request.
It's probably best to set a breakpoint on the line where the response variable is declared, press F10 once, then hover over the variable, so you can see what data is available for you to use in the response.
I was hoping just to add a comment to the answer for v3 Beta, but rep points prevent that. However, I thought it would be nice for others to have this information so here it is:
using Google.Apis.Authentication.OAuth2;
using Google.Apis.Authentication.OAuth2.DotNetOpenAuth;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Services;
These name spaces are used throughout the code in that post. I always wish people would post name spaces more often, I seem to spend a good bit of time looking for them. I hope this saves some people a few minutes of work.
I've setup something pretty similar to the above answer in a nuGet package. It does the following:
- connects to a "Service Account" you set up in the API Console
- Pulls any Google Analytics data you would like
- Displays that data using Google's Charts API
and it does all of this in a very easy to modify way. You can see more here: https://www.nuget.org/packages/GoogleAnalytics.GoogleCharts.NET/.
Hope google will provide proper documentation someday. Here I am listing all the steps to integrate google analytics server side authentication in ASP.NET C#.
Step 1: Create a project in google console
Goto the link https://console.developers.google.com/iam-admin/projects and create a project by clicking on "Create Project" button and provide project name in the pop up window and submit it.
Step 2: Create credentials and service account
After creation of the project, you will be redirected to "API Manager" page.
Click on credentials and press "Create Credentials" button. select "service account key" from dropdown you will be redirected to next page.
In the service account drop down, select "New service account". Fill in the service account name and download the p12 key. It will have p12 extension. you will get a popup having a password "notasecret" which is default and your private key will be downloaded.
Step 3: Create 0auth client ID
click on the "create credentials" dropdown and select "0auth client ID" you will be redirected to "0auth consent screen" tab. provide a random name in the project name textbox. select application type as "Web application" and click create button. Copy the generated client ID in a notepad.
Step 4: Enable the APIs
On the left side click on the "Overview" tab and select "Enabled APIs" from the horizontal tab. In the search bar search for "Analytics API" click on the dropdown and press "Enable" button. Now again search for "Analytics Reporting V4" and enable it.
Step 5: Install nuget packages
In visual studio, Go to Tools > Nuget Package Manager > Package Manager Console.
Copy paste the below code in the console to install the nuget packages.
Install-Package Google.Apis.Analytics.v3
Install-Package DotNetOpenAuth.Core -Version 4.3.4.13329
The above two packages are Google analytics and DotNetOpenAuth nuget packages.
Step 6: Provide 'View and Analyze' permission to service account
Go to google analytics account and click on "Admin" tab and select "User Management" from left menus,select a domain of which you want to access analytics data and insert the service account email id under it and select "Read and Analyze" permission from the dropdown. Service account email id looks like ex: googleanalytics#googleanalytics.iam.gserviceaccount.com.
Working Code
FRONT END CODE:
Copy and paste the below analytics embed script in your front end or else you can get this code from google analytics documentation page also.
<script>
(function (w, d, s, g, js, fs) {
g = w.gapi || (w.gapi = {}); g.analytics = { q: [], ready: function (f) { this.q.push(f); } };
js = d.createElement(s); fs = d.getElementsByTagName(s)[0];
js.src = 'https://apis.google.com/js/platform.js';
fs.parentNode.insertBefore(js, fs); js.onload = function () { g.load('analytics'); };
}(window, document, 'script'));</script>
Paste the below code in the body tag of your front end page.
<asp:HiddenField ID="accessToken" runat="server" />
<div id="chart-1-container" style="width:600px;border:1px solid #ccc;"></div>
<script>
var access_token = document.getElementById('<%= accessToken.ClientID%>').value;
gapi.analytics.ready(function () {
/**
* Authorize the user with an access token obtained server side.
*/
gapi.analytics.auth.authorize({
'serverAuth': {
'access_token': access_token
}
});
/**
* Creates a new DataChart instance showing sessions.
* It will be rendered inside an element with the id "chart-1-container".
*/
var dataChart1 = new gapi.analytics.googleCharts.DataChart({
query: {
'ids': 'ga:53861036', // VIEW ID <-- Goto your google analytics account and select the domain whose analytics data you want to display on your webpage. From the URL ex: a507598w53044903p53861036. Copy the digits after "p". It is your view ID
'start-date': '2016-04-01',
'end-date': '2016-04-30',
'metrics': 'ga:sessions',
'dimensions': 'ga:date'
},
chart: {
'container': 'chart-1-container',
'type': 'LINE',
'options': {
'width': '100%'
}
}
});
dataChart1.execute();
/**
* Creates a new DataChart instance showing top 5 most popular demos/tools
* amongst returning users only.
* It will be rendered inside an element with the id "chart-3-container".
*/
});
</script>
You can also get your View ID from https://ga-dev-tools.appspot.com/account-explorer/
BACK END CODE:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web.Script.Serialization;
using System.Net;
using System.Text;
using Google.Apis.Analytics.v3;
using Google.Apis.Analytics.v3.Data;
using Google.Apis.Services;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Util;
using DotNetOpenAuth.OAuth2;
using System.Security.Cryptography;
namespace googleAnalytics
{
public partial class api : System.Web.UI.Page
{
public const string SCOPE_ANALYTICS_READONLY = "https://www.googleapis.com/auth/analytics.readonly";
string ServiceAccountUser = "googleanalytics#googleanalytics.iam.gserviceaccount.com"; //service account email ID
string keyFile = #"D:\key.p12"; //file link to downloaded key with p12 extension
protected void Page_Load(object sender, EventArgs e)
{
string Token = Convert.ToString(GetAccessToken(ServiceAccountUser, keyFile, SCOPE_ANALYTICS_READONLY));
accessToken.Value = Token;
var certificate = new X509Certificate2(keyFile, "notasecret", X509KeyStorageFlags.Exportable);
var credentials = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(ServiceAccountUser)
{
Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly }
}.FromCertificate(certificate));
var service = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = "Google Analytics API"
});
string profileId = "ga:53861036";
string startDate = "2016-04-01";
string endDate = "2016-04-30";
string metrics = "ga:sessions,ga:users,ga:pageviews,ga:bounceRate,ga:visits";
DataResource.GaResource.GetRequest request = service.Data.Ga.Get(profileId, startDate, endDate, metrics);
GaData data = request.Execute();
List<string> ColumnName = new List<string>();
foreach (var h in data.ColumnHeaders)
{
ColumnName.Add(h.Name);
}
List<double> values = new List<double>();
foreach (var row in data.Rows)
{
foreach (var item in row)
{
values.Add(Convert.ToDouble(item));
}
}
values[3] = Math.Truncate(100 * values[3]) / 100;
txtSession.Text = values[0].ToString();
txtUsers.Text = values[1].ToString();
txtPageViews.Text = values[2].ToString();
txtBounceRate.Text = values[3].ToString();
txtVisits.Text = values[4].ToString();
}
public static dynamic GetAccessToken(string clientIdEMail, string keyFilePath, string scope)
{
// certificate
var certificate = new X509Certificate2(keyFilePath, "notasecret");
// header
var header = new { typ = "JWT", alg = "RS256" };
// claimset
var times = GetExpiryAndIssueDate();
var claimset = new
{
iss = clientIdEMail,
scope = scope,
aud = "https://accounts.google.com/o/oauth2/token",
iat = times[0],
exp = times[1],
};
JavaScriptSerializer ser = new JavaScriptSerializer();
// encoded header
var headerSerialized = ser.Serialize(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = Convert.ToBase64String(headerBytes);
// encoded claimset
var claimsetSerialized = ser.Serialize(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = Convert.ToBase64String(claimsetBytes);
// input
var input = headerEncoded + "." + claimsetEncoded;
var inputBytes = Encoding.UTF8.GetBytes(input);
// signature
var rsa = certificate.PrivateKey as RSACryptoServiceProvider;
var cspParam = new CspParameters
{
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
};
var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = Convert.ToBase64String(signatureBytes);
// jwt
var jwt = headerEncoded + "." + claimsetEncoded + "." + signatureEncoded;
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var uri = "https://accounts.google.com/o/oauth2/token";
var content = new NameValueCollection();
content["assertion"] = jwt;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content));
var result = ser.Deserialize<dynamic>(response);
object pulledObject = null;
string token = "access_token";
if (result.ContainsKey(token))
{
pulledObject = result[token];
}
//return result;
return pulledObject;
}
private static int[] GetExpiryAndIssueDate()
{
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var issueTime = DateTime.UtcNow;
var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
var exp = (int)issueTime.AddMinutes(55).Subtract(utc0).TotalSeconds;
return new[] { iat, exp };
}
}
}
Another Working Approach
Add below code in the ConfigAuth
var googleApiOptions = new GoogleOAuth2AuthenticationOptions()
{
AccessType = "offline", // can use only if require
ClientId = ClientId,
ClientSecret = ClientSecret,
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new Claim("Google_AccessToken", context.AccessToken));
if (context.RefreshToken != null)
{
context.Identity.AddClaim(new Claim("GoogleRefreshToken", context.RefreshToken));
}
context.Identity.AddClaim(new Claim("GoogleUserId", context.Id));
context.Identity.AddClaim(new Claim("GoogleTokenIssuedAt", DateTime.Now.ToBinary().ToString()));
var expiresInSec = 10000;
context.Identity.AddClaim(new Claim("GoogleTokenExpiresIn", expiresInSec.ToString()));
return Task.FromResult(0);
}
},
SignInAsAuthenticationType = DefaultAuthenticationTypes.ApplicationCookie
};
googleApiOptions.Scope.Add("openid"); // Need to add for google+
googleApiOptions.Scope.Add("profile");// Need to add for google+
googleApiOptions.Scope.Add("email");// Need to add for google+
googleApiOptions.Scope.Add("https://www.googleapis.com/auth/analytics.readonly");
app.UseGoogleAuthentication(googleApiOptions);
Add below code, name spaces and relative references
using Google.Apis.Analytics.v3;
using Google.Apis.Analytics.v3.Data;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Services;
using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security;
using System;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
public class HomeController : Controller
{
AnalyticsService service;
public IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
public async Task<ActionResult> AccountList()
{
service = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = await GetCredentialForApiAsync(),
ApplicationName = "Analytics API sample",
});
//Account List
ManagementResource.AccountsResource.ListRequest AccountListRequest = service.Management.Accounts.List();
//service.QuotaUser = "MyApplicationProductKey";
Accounts AccountList = AccountListRequest.Execute();
return View();
}
private async Task<UserCredential> GetCredentialForApiAsync()
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = ClientId,
ClientSecret = ClientSecret,
},
Scopes = new[] { "https://www.googleapis.com/auth/analytics.readonly" }
};
var flow = new GoogleAuthorizationCodeFlow(initializer);
var identity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ApplicationCookie);
if (identity == null)
{
Redirect("/Account/Login");
}
var userId = identity.FindFirstValue("GoogleUserId");
var token = new TokenResponse()
{
AccessToken = identity.FindFirstValue("Google_AccessToken"),
RefreshToken = identity.FindFirstValue("GoogleRefreshToken"),
Issued = DateTime.FromBinary(long.Parse(identity.FindFirstValue("GoogleTokenIssuedAt"))),
ExpiresInSeconds = long.Parse(identity.FindFirstValue("GoogleTokenExpiresIn")),
};
return new UserCredential(flow, userId, token);
}
}
Add this in the Application_Start() in Global.asax
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
I am trying to add a simple log in with Facebook button to my ASP.NET (C#) website. All I need is on the server side to retrieve the Facebook user's email address once they have logged in.
I was trying to use this example but it seems that the cookie "fbs_appid" is no longer used and instead there is one called "fbsr_appid".
How can I change the sample to use the different cookie? Alternately does anyone have a working example of retrieving the logged in Facebook user's email address.
I know there is an SDK I can use but I want to keep things simple. The above example would be perfect if it worked.
I managed to get the information needed using the fbsr cookie. I created the following class which does all of the work to confirm the user logged in with Facebook and then retrieves the user's details:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Serialization;
namespace HarlequinShared
{
public class FacebookLogin
{
protected static string _appId = null;
protected static string AppId
{
get
{
if (_appId == null)
_appId = ConfigurationManager.AppSettings["FacebookAppId"] ?? null;
return _appId;
}
}
protected static string _appSecret = null;
protected static string AppSecret
{
get
{
if (_appSecret == null)
_appSecret = ConfigurationManager.AppSettings["FacebookAppSecret"] ?? null;
return _appSecret;
}
}
public static FacebookUser CheckLogin()
{
string fbsr = HttpContext.Current.Request.Cookies["fbsr_" + AppId].Value;
int separator = fbsr.IndexOf(".");
if (separator == -1)
{
return null;
}
string encodedSig = fbsr.Substring(0, separator);
string payload = fbsr.Substring(separator + 1);
string sig = Base64Decode(encodedSig);
var serializer = new JavaScriptSerializer();
Dictionary<string, string> data = serializer.Deserialize<Dictionary<string, string>>(Base64Decode(payload));
if (data["algorithm"].ToUpper() != "HMAC-SHA256")
{
return null;
}
HMACSHA256 crypt = new HMACSHA256(Encoding.ASCII.GetBytes(AppSecret));
crypt.ComputeHash(Encoding.UTF8.GetBytes(payload));
string expectedSig = Encoding.UTF8.GetString(crypt.Hash);
if (sig != expectedSig)
{
return null;
}
string accessTokenResponse = FileGetContents("https://graph.facebook.com/oauth/access_token?client_id=" + AppId + "&redirect_uri=&client_secret=" + AppSecret + "&code=" + data["code"]);
NameValueCollection options = HttpUtility.ParseQueryString(accessTokenResponse);
string userResponse = FileGetContents("https://graph.facebook.com/me?access_token=" + options["access_token"]);
userResponse = Regex.Replace(userResponse, #"\\u([\dA-Fa-f]{4})", v => ((char)Convert.ToInt32(v.Groups[1].Value, 16)).ToString());
FacebookUser user = new FacebookUser();
Regex getValues = new Regex("(?<=\"email\":\")(.+?)(?=\")");
Match infoMatch = getValues.Match(userResponse);
user.Email = infoMatch.Value;
getValues = new Regex("(?<=\"first_name\":\")(.+?)(?=\")");
infoMatch = getValues.Match(userResponse);
user.FirstName = infoMatch.Value;
getValues = new Regex("(?<=\"last_name\":\")(.+?)(?=\")");
infoMatch = getValues.Match(userResponse);
user.LastName = infoMatch.Value;
return user;
}
protected static string FileGetContents(string url)
{
string result;
WebResponse response;
WebRequest request = HttpWebRequest.Create(url);
response = request.GetResponse();
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
result = sr.ReadToEnd();
sr.Close();
}
return result;
}
protected static string Base64Decode(string input)
{
UTF8Encoding encoding = new UTF8Encoding();
string encoded = input.Replace("=", string.Empty).Replace('-', '+').Replace('_', '/');
var decoded = Convert.FromBase64String(encoded.PadRight(encoded.Length + (4 - encoded.Length % 4) % 4, '='));
var result = encoding.GetString(decoded);
return result;
}
}
public class FacebookUser
{
public string UID { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
And then I can use this in my login page:
FacebookUser user = FacebookLogin.CheckLogin();
if (user != null)
{
Response.Write("<p>" + user.Email);
Response.Write("<p>" + user.FirstName);
Response.Write("<p>" + user.LastName);
}
This is further explained here.
I believe that this method is secure and does the job as simply as possible. Please comment if there is any concerns.
This is not at all how you should be doing things, especially if you are trying to do it on the server side. You should not use the cookie.
The different sdks make things simpler, they (especially the official ones) stay up to date with facebook changes, unlike the example you provided that just stopped working when the cookie name was changed.
I'm not a C# developer, and never tried to use facebook from that environment so I can't tell you exactly how to do it with C#, but this is the main concept:
When you send the user for authentication (assumed Server Side Flow) you need to ask the user for permissions for anything but basic and public user information.
The email field is filed under "extended permission" and you need to ask for it specifically, something like this:
https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_url=YOUR_REDIRECT_URI&scope=email
(You can read more about permissions here)
Once the user authorized your app and granted you with the permissions, you can then ask for that data.
You can do it in the server side by asking for the "/me" graph path (or just the email: "me?fields=email").
You can also do it in the client side with the javascript sdk:
FB.api("/me", function(response) {
console.log(response);
});