NavigateToTest VS2015 extension

You can download the extension here:
NavigateToTest Visual Studio 2015 extension

Here’s the latest version that supports Visual Studio 2015.

Extension is convention based. It matches ClassName file with ClassNameTest or ClassNameTests and vice-versa, so you can easily navigate to the test file and back.

Here are some screenshots:

Here’s the toolbar and two opened files:

You can download the extension here:
NavigateToTest Visual Studio 2015 extension

Logging in Mail.dll email client

This article provides a comprehensive guide on how to perform logging within the Mail.dll .NET email client. It outlines step-by-step instructions for implementing robust logging mechanisms, enhancing troubleshooting capabilities, and tracking email communication effectively.

By following the methods detailed in the article, VisualBasic and C# developers can seamlessly integrate logging features into their applications, gaining valuable insights into the Imap, Pop3 and Smtp clients behavior and facilitating streamlined debugging processes.

To enable logging for all Mail.dll .NET clients (Imap, Pop3, Smtp) you only need to add the following line before you connect:

// C# version:

Limilabs.Mail.Log.Enabled = true;
' VB.NET version:

Limilabs.Mail.Log.Enabled = True

You can observe the log output by:

  • looking at the Visual Studio’s output window (View/Output/’Show output from’: Debug)
  • subscribing to Log.WriteLine event
  • defining custom listeners using your application’s config file (App.config or Web.config)
  • using log4net

This is how the log looks like in the Visual Studio’s output window:

For regular .NET framework you can also enable logging using your application’s config file (App.config, Web.config):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>

      <switches>
        <add name="Mail.dll" value="Verbose" />
      </switches>

    </system.diagnostics>
</configuration>

Logging with log4net

If you are using log4net, Mail.dll is going to use log4net instead of standard .NET System.Net.TraceSource class. Please refer to log4net manual on how to capture log entries.

Mail.dll uses logger called “Mail.dll”

_logger = LogManager.GetLogger("Mail.dll")

and level Info _logger.Info(message) to log information.

Please remember that even when using log4net, you need to enable logging by setting “Limilabs.Mail.Log.Enabled = True” or by setting Mail.dll trace switch in the config file (App.config, Web.config) to Verbose as shown above.

Log.WriteLine

Limilabs.Mail.Log class exposes WriteLine event. You can use that event to subscribe your own logging library in both .NET and .NET framework.

// C#

Limilabs.Mail.Log.WriteLine += Console.WriteLine;
' VB.NET

AddHandler Limilabs.Mail.Log.WriteLine, AddressOf Console.WriteLine

Log to file (.NET framework)

You’ll need to define a TextWriterTraceListener that writes to a file and connect it with Mail.dll trace source. The easiest solution is to modify your application’s config file App.config or Web.config accordingly:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>

        <trace autoflush="true"/>

        <switches>
            <add name="Mail.dll" value="Verbose"/>
        </switches>

        <sources>
            <source name="Mail.dll">
                <listeners>
                    <add name="MailLogFile"/>
                </listeners>
            </source>
        </sources>

        <sharedListeners>
            <add
                name="MailLogFile"
                type="System.Diagnostics.TextWriterTraceListener"
                initializeData="c:\folder-with-write-access\mail.log"/>
        </sharedListeners>

    </system.diagnostics>
</configuration>

OAuth 2.0 with Gmail over IMAP for web applications (DotNetOpenAuth)

Consider using Google.Apis version for desktop applications instead of DotNetOpenAuth version.

OAuth is an open protocol to allow secure API authorization in a simple and standard method from desktop and web applications.

This article describes using OAuth 2.0 to access Gmail IMAP and SMTP servers using .NET IMAP component in web application scenario (ASP.NET/ASP.NET MVC). You can also use OAuth 2.0 for installed applications.

DotNetOpenAuth

First download the latest version of DotNetOpenAuth – it’s free, open source library that implements OAuth 2.0: http://www.dotnetopenauth.net

Add it as a reference and import namespaces:

// c#

using DotNetOpenAuth.OAuth2;
using DotNetOpenAuth.OAuth2.Messages;
using DotNetOpenAuth.Messaging;
' VB.NET 

Imports DotNetOpenAuth.OAuth2
Imports DotNetOpenAuth.OAuth2.Messages
Imports DotNetOpenAuth.Messaging

Register Application

Before you can use OAuth 2.0, you must register your application using the Google Developers Console. After you’ve registered, go to the API Access tab and copy the “Client ID” and “Client secret” values and specify “Redirect URI“, which you’ll need later.

At least product name must be specified:

Now create credentials:

Specify redirect URI:

After you’ve registered, copy the “Client ID” and “Client secret” values, which you’ll need later:

Now we can define clientID, clientSecret, redirect url and scope variables, as well as Google OAuth 2.0 server addresses. Scope basically specifies what services we want to have access to. In our case it is user’s email address and IMAP/SMTP access:

const string clientID = "12345.apps.googleusercontent.com";
const string clientSecret = "XXXYYY111";
const string redirectUri = "http://www.yourdomain.com/oauth2callback";

AuthorizationServerDescription server = new AuthorizationServerDescription
    {
        AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
        TokenEndpoint = new Uri("https://oauth2.googleapis.com/token"),
        ProtocolVersion = ProtocolVersion.V20,
    };
List<string> scope = new List<string>
    {
        GoogleScope.ImapAndSmtp.Name,
        GoogleScope.UserInfoEmailScope.Name
    };

Obtain an OAuth 2.0 access token

As we are using ASP.NET we’ll use WebServerClient class:

WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);

// Here redirect to authorization site occurs
consumer.RequestUserAuthorization(scope, new Uri(redirectUri));

If you use ASP.NET MVC the last line is different:

// Here redirect to authorization site occurs
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
    scope, new Uri(redirectUri));

return response.AsActionResult();

At this point user is redirected to Google to authorize the access:

After this step user is redirected back to your website (http://www.yourdomain.com/oauth2callback). Following is this callback code. Its purpose is to get a refresh-token and an access-token:

WebServerClient consumer= new WebServerClient(server, clientID, clientSecret);
consumer.ClientCredentialApplicator =
    ClientCredentialApplicator.PostParameter(clientSecret);
IAuthorizationState grantedAccess = consumer.ProcessUserAuthorization(null);

string accessToken = grantedAccess.AccessToken;

An access token is usually valid for a maximum of one hour, and allows you to access the user’s data. You also received a refresh token. A refresh token can be used to request a new access token once the previous expired.

Access IMAP/SMTP server

Finally we’ll ask Google for user’s email and use LoginOAUTH2 method to access Gmail’s IMAP server:

GoogleApi api = new GoogleApi(accessToken);
string user = api.GetEmail();

using (Imap imap = new Imap())
{
    imap.ConnectSSL("imap.gmail.com");
    imap.LoginOAUTH2(user, accessToken);

    imap.SelectInbox();
    List<long> uids = imap.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        var eml = imap.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }
    imap.Close();
}

Refreshing access token

An access token is usually short lived and valid for a maximum of one hour. The main reason behind this is security and prevention of replay attacks. This means that for long-lived applications you need to refresh the access token.

In most cases web applications don’t need to refresh access token (they request new one every time), thus when using WebServerClient refresh token is not sent. To force sending refresh token you need to set access_type url parameter to offline:

AuthorizationServerDescription authServer = new AuthorizationServerDescription
{
    AuthorizationEndpoint =
        new Uri("https://accounts.google.com/o/oauth2/auth?access_type=offline"),
    ...
};

Your refresh token will be sent only once – don’t loose it!

We recommend storing entire IAuthorizationState object received from WebServerClient.ProcessUserAuthorization method call. This object contains both: refresh token and access token, along with its expiration time.

The process of refreshing access token is simple:

IAuthorizationState grantedAccess = ...
consumer.RefreshAuthorization(grantedAccess, TimeSpan.FromMinutes(20));

In the example above the access token will not be refreshed if its remaining lifetime exceeds 20 minutes.

Retrieving lost refresh token

When your application receives a refresh token, it is important to store that refresh token for future use. If your application loses the refresh token, it will have to re-prompt the user for consent before obtaining another refresh token.

You’ll need to add approval_prompt=force to your parameters:

AuthorizationServerDescription authServer = new AuthorizationServerDescription
 {
     AuthorizationEndpoint =
          new Uri("https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force"),
     ...
 };

OAuth 2.0 with Gmail over IMAP for service account (DotNetOpenAuth)

Consider using Google.Apis version for service account Gmail access instead of DotNetOpenAuth version.

In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0, .NET IMAP component and service accounts. The basic idea is that domain administrator can use this method to access user email without knowing user’s password.

This scenario is very similar to 2-legged OAuth, which uses OAuth 1.0a. Although it still works, it has been deprecated by Google and OAuth 2.0 service accounts were introduced.

The following describes how to use XOAUTH2 and OAuth 2.0 to achieve the equivalent of 2-legged OAuth.

Google APIs console

First you need to visit the Google Developers Console and create a service account:

console_0
console_1
console_2

Download and save this private key, you’ll need that later:

console_3

Go to “Service accounts” and click “View Client ID”:

console_4

Make a note of the Client ID and Email address (Service account).

Google Apps Dashboard

Next step is to authorize access for newly created service account.

Visit your domain administration panel:
https://www.google.com/a/cpanel/yourdomain.com/ManageOauthClients

Then click “Advanced tools”, “Authentication” and “Manage third party OAuth Client access”.

On this screen you can authorize service account to access email scope:

cpanel_1

Use previously remembered Client ID and “https://mail.google.com/”, which is IMAP/SMTP API scope:

Client Name: 1234567890
One or More API Scopes: https://mail.google.com/

DotNetOpenAuth

DotNetOpenAuth is free, open source library that implements OAuth 2.0.

However you can not use the latest version. This is because Google has not updated their code to work with the most recent release. You’ll need to use 4.0 version.

Newtonsoft.Json

The other library you’ll need is Newtonsoft.Json JSON library. You can download it here: https://github.com/JamesNK/Newtonsoft.Json/releases or use nuget.

Access IMAP/SMTP server

using System;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Newtonsoft.Json;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2;
using DotNetOpenAuth.OAuth2.Messages;
using Limilabs.Client.Authentication.Google;
using Limilabs.Client.IMAP;
using Limilabs.Mail;

const string serviceAccountEmail = "name@xxxxxxxxxx.gserviceaccount.com";
const string serviceAccountCertPath = @"c:\XYZ.p12";
const string serviceAccountCertPassword = "notasecret";
const string userEmail = "user@your-domain.com";

X509Certificate2 certificate =  new X509Certificate2(
    serviceAccountCertPath,
    serviceAccountCertPassword,
    X509KeyStorageFlags.Exportable);

AuthorizationServerDescription server = new AuthorizationServerDescription
    {
        AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
        TokenEndpoint = new Uri("https://oauth2.googleapis.com/token"),
        ProtocolVersion = ProtocolVersion.V20,
    };

AssertionFlowClient provider = new AssertionFlowClient(server, certificate)
{
    ServiceAccountId = serviceAccountEmail,
    Scope = Limilabs.Client.Authentication.Google.GoogleScope.ImapAndSmtp.Name,
    ServiceAccountUser = userEmail,
};

IAuthorizationState grantedAccess = AssertionFlowClient.GetState(provider);
string accessToken = grantedAccess.AccessToken;

using (Imap client = new Imap())
{
    client.ConnectSSL("imap.gmail.com");
    client.LoginOAUTH2(userEmail, accessToken);

    client.SelectInbox();
    List<long> uids = client.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        var eml = client.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }
    client.Close();
}




AssertionFlowClient

I’ll need to add several classes defined below. You can also browse Google APIs Client Library for .NET.

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Limilabs.Client;
using Limilabs.Client.Authentication.SSPI.Ntlm;

/// <summary>
/// Assertion flow header used to generate the assertion flow message header.
/// </summary>
public class AssertionFlowHeader
{
    /// <summary>
    /// Gets or sets the encryption algorithm used by the assertion flow message.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("alg")]
    public String Algorithm { get; set; }

    /// <summary>
    /// Gets or sets the type of the claim.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("typ")]
    public String Type { get; set; }
};

/// <summary>
/// Google assertion flow header holding Google supported values.
/// </summary>
public class GoogleAssertionFlowHeader : AssertionFlowHeader
{

    /// <summary>
    /// The google signing algorithm, currently RSA-SHA256
    /// </summary>
    public const string GoogleSigningAlgorithm = "RS256";

    /// <summary>
    /// The type of the google assertion, currently JSON Web Token
    /// </summary>
    public const string GoogleAssertionType = "JWT";

    public GoogleAssertionFlowHeader()
    {
        Algorithm = GoogleSigningAlgorithm;
        Type = GoogleAssertionType;
    }
};

/// <summary>
/// Assertion flow claim used to generate the assertion flow message claim.
/// </summary>
public class AssertionFlowClaim
{
    public AssertionFlowClaim()
    {
        DateTime begin = new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc);
        IssuedAt = (long)(DateTime.UtcNow - begin).TotalSeconds;
        ExpiresAt = IssuedAt + 3600;
    }

    public AssertionFlowClaim(AuthorizationServerDescription authorizationServer)
        : this()
    {
        Audience = authorizationServer.TokenEndpoint.ToString();
    }

    /// <summary>
    /// Gets or sets the assertion flow issuer (e.g client ID).
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("iss")]
    public String Issuer { get; set; }

    /// <summary>
    /// Gets or sets the service account user (for domain-wide delegation).
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("prn")]
    public String Principal { get; set; }

    /// <summary>
    /// Gets or sets the scope.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("scope")]
    public String Scope { get; set; }

    /// <summary>
    /// Gets or sets the token endpoint.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("aud")]
    public String Audience { get; set; }

    /// <summary>
    /// Gets or sets the expected expiration of the token to retrieve.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("exp")]
    public long ExpiresAt { get; set; }

    /// <summary>
    /// Gets or sets the UTC timestamp at which this claim has been built.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("iat")]
    public long IssuedAt { get; set; }
};

/// <summary>
/// Assertion flow message to be sent to the token endpoint.
/// </summary>
public class AssertionFlowMessage : MessageBase
{
    /// <summary>
    /// Google supported assertion type
    /// </summary>
    public const string GoogleAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer";

    /// <summary>
    /// Initializes a new instance of the <see cref="AssertionFlowMessage"/> class.
    /// </summary>
    /// <param name='authorizationServer'> Authorization server description. </param>
    public AssertionFlowMessage(AuthorizationServerDescription authorizationServer) :
        base(new Version(2, 0), MessageTransport.Direct, authorizationServer.TokenEndpoint)
    {
        GrantType = "assertion";
        AssertionType = GoogleAssertionType;
        this.HttpMethods = HttpDeliveryMethods.PostRequest;
    }

    /// <summary>
    /// Gets or sets the type of the grant (defaults to "assertion").
    /// </summary>
    [MessagePart("grant_type", IsRequired = true)]
    public String GrantType { get; set; }

    /// <summary>
    /// Gets or sets the type of the assertion
    /// (defaults to "http://oauth.net/grant_type/jwt/1.0/bearer").
    /// </summary>
    [MessagePart("assertion_type", IsRequired = true)]
    public String AssertionType { get; set; }

    /// <summary>
    /// Gets or sets the assertion message.
    /// </summary>
    [MessagePart("assertion", IsRequired = true)]
    public String Assertion { get; set; }
};

public class AssertionFlowClient : ClientBase
{
    /// <summary>
    /// Gets or sets the service account identifier.
    /// </summary>
    /// <value>
    /// The service account identifier.
    /// </value>
    public String ServiceAccountId { get; set; }

    /// <summary>
    /// Gets or sets the service account user (used for domain-wide delegation).
    /// </summary>
    public String ServiceAccountUser { get; set; }

    /// <summary>
    /// Gets or sets the scope to get access for.
    /// </summary>
    public String Scope { get; set; }

    /// <summary>
    /// Gets the certificate used to sign the assertion.
    /// </summary>
    public X509Certificate2 Certificate { get; private set; }

    /// <summary>
    /// Gets or sets the JWT claim's header (defaults to Google's supported values).
    /// </summary>
    public AssertionFlowHeader Header { get; set; }

    public RSACryptoServiceProvider Key { get; private set; }

    /// <summary>
    /// Initializes a new instance of the
    /// <see cref="AssertionFlowClient"/> class.
    /// </summary>
    /// <param name='authorizationServer'>
    /// Authorization server description.
    /// </param>
    /// <param name='certificate'>
    /// Certificate to use to sign the assertion flow messages.
    /// </param>
    public AssertionFlowClient(
        AuthorizationServerDescription authorizationServer,
        X509Certificate2 certificate)
        : base(authorizationServer, null, null)
    {
        if (certificate == null)
            throw new ArgumentNullException("certificate");
        if (certificate.PrivateKey == null)
            throw new ArgumentNullException("certificate.PrivateKey");

        Header = new GoogleAssertionFlowHeader();
        Certificate = certificate;

        // Workaround to correctly cast the private key as a RSACryptoServiceProvider type 24
        RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
        byte[] privateKeyBlob = rsa.ExportCspBlob(true);

        Key = new RSACryptoServiceProvider();
        Key.ImportCspBlob(privateKeyBlob);
    }

    /// <summary>
    /// Helper method to retrieve the Authorization State.
    /// </summary>
    /// <returns>
    /// The authorization state.
    /// </returns>
    /// <param name='provider'>
    /// The provider to use to retrieve the authorization state.
    /// </param>
    public static IAuthorizationState GetState(AssertionFlowClient provider)
    {
        if (provider.Scope == null)
            throw new ArgumentNullException("Scope");
        IAuthorizationState state = new AuthorizationState(provider.Scope.Split(' '));

        if (provider.RefreshToken(state, null))
        {
            return state;
        }
        return null;
    }

    /// <summary>
    /// Request a new access token using the OAuth 2.0 assertion flow.
    /// </summary>
    /// <returns>
    /// Whether or not a new access token has been successfully retrieved.
    /// </returns>
    /// <param name='authorization'>
    /// Object containing the current authorization state.
    /// </param>
    /// <param name='skipIfUsefulLifeExceeds'>
    /// If set to <c>true</c> skip if useful life exceeds.
    /// </param>
    public new bool RefreshToken(
        IAuthorizationState authorization,
        TimeSpan? skipIfUsefulLifeExceeds)
    {
        return RefreshToken(authorization, skipIfUsefulLifeExceeds, this.Channel.Request);
    }

    public bool RefreshToken(
        IAuthorizationState authorization,
        TimeSpan? skipIfUsefulLifeExceeds,
        Func<IDirectedProtocolMessage, IProtocolMessage> requestProvider)
    {
        if (authorization == null)
            throw new ArgumentNullException("authorization");
        if (this.Certificate == null)
            throw new ArgumentNullException("Certificate");

        // Check if the token is still valid.
        if (skipIfUsefulLifeExceeds.HasValue && authorization.AccessTokenExpirationUtc.HasValue)
        {
            TimeSpan timeSpan = authorization.AccessTokenExpirationUtc.Value - DateTime.UtcNow;
            if (timeSpan > skipIfUsefulLifeExceeds.Value)
            {
                return false;
            }
        }

        AssertionFlowMessage requestMessage = GenerateMessage();

        var response = requestProvider(requestMessage);

        // Response is not strongly-typed to an AccessTokenSuccessResponse because DotNetOpenAuth can't infer the
        // type from the request message type. The only way to get access to the result data is through the
        // resulting Dictionary.
        if (response.ExtraData.ContainsKey("access_token") && response.ExtraData.ContainsKey("expires_in"))
        {
            authorization.AccessToken = response.ExtraData["access_token"];
            long expiresIn = long.Parse(response.ExtraData["expires_in"]);
            DateTime utcNow = DateTime.UtcNow;
            authorization.AccessTokenExpirationUtc = utcNow.AddSeconds(expiresIn);
            authorization.AccessTokenIssueDateUtc = utcNow;
            authorization.SaveChanges();
            return true;
        }
        return false;
    }

    /// <summary>
    /// Generates the assertion flow message to be sent to the token endpoint.
    /// </summary>
    /// <returns>
    /// The assertion flow message.
    /// </returns>
    private AssertionFlowMessage GenerateMessage()
    {
        string header = JsonConvert.SerializeObject(Header);
        string claim = JsonConvert.SerializeObject(
            new AssertionFlowClaim(AuthorizationServer)
            {
                Issuer = this.ServiceAccountId,
                Principal = this.ServiceAccountUser,
                Scope = this.Scope
            });

        StringBuilder assertion = new StringBuilder();
        assertion.Append(UnpaddedUrlSafeBase64Encode(header));
        assertion.Append(".");
        assertion.Append(UnpaddedUrlSafeBase64Encode(claim));

        // TODO: Check if this is working on FIPS enabled systems.
        byte[] data = Encoding.ASCII.GetBytes(assertion.ToString());
        String signature = UnpaddedUrlSafeBase64Encode(Key.SignData(data, "SHA256"));
        assertion.Append(".");
        assertion.Append(signature);

        return new AssertionFlowMessage(this.AuthorizationServer)
        {
            Assertion = assertion.ToString()
        };
    }

    /// <summary>
    /// Encode the provided UTF8 string into an URL safe base64 string.
    /// </summary>
    /// <returns>
    /// The URL safe base64 string.
    /// </returns>
    /// <param name='value'>
    /// String to encode.
    /// </param>
    private String UnpaddedUrlSafeBase64Encode(String value)
    {
        return UnpaddedUrlSafeBase64Encode(Encoding.UTF8.GetBytes(value));
    }

    /// <summary>
    /// Encode the byte array into an URL safe base64 string.
    /// </summary>
    /// <returns>
    /// The URL safe base64 string.
    /// </returns>
    /// <param name='bytes'>
    /// Bytes to encode.
    /// </param>
    private String UnpaddedUrlSafeBase64Encode(Byte[] bytes)
    {
        return Convert.ToBase64String(bytes)
            .Replace("=", String.Empty)
            .Replace('+', '-')
            .Replace('/', '_');
    }
};

Helpful POP3 and IMAP Exchange 2013 links

Here is the list of some helpful links regarding IMAP and POP3 protocols in Exchange 2013:

Enable IMAP4 in Exchange 2013

Enable POP3 in Exchange 2013

POP3 and IMAP4

Public folders in Exchange 2013
Public folders and IMAP

Shared mailboxes in Exchange 2013
Accessing shared and delegated mailboxes