Thursday, September 26, 2013

ArcGIS Online Authentication with OAuth2 and Windows Store

image

User authentication is an important part of application design and development.  Authentication allows developers protect user privacy, store settings and access profile information such as a profile picture and other assorted details.

ArcGIS Online (or AGOL) is Esri’s cloud based solution for creating and sharing maps, apps and geographic services.  AGOL supports OAuth2, an open standard for user authentication.  This blog posting contains a code snippet that can help you connect to AGOL using OAuth2 authentication.  Please keep in mind that this functionality will become a core capability of the next release of Esri’s ArcGIS Runtime SDK for Windows Store apps.

In this example, when the user clicks the blue start button (see screenshot above) the following dialog appears.  If the user enters a valid username and password, the login dialog is dismissed and the app will navigate from the welcome page to the main application page.

image

Additional References:

 

public sealed partial class WelcomePage : LayoutAwarePage {
    private const string AGOL_OAUTH = "https://www.arcgis.com/sharing/oauth2/authorize";
    private const string AGOL_APPID = "<your app id>";
    private const string AGOL_REDIR = "http://maps.esri.com";
    private const string AGOL_GETTK = "https://www.arcgis.com/sharing/oauth2/token";
    //
    // CONSTRUCTOR
    //
    public WelcomePage() {
        this.InitializeComponent();

        // Button events
        this.ButtonLogin.Click += async (s, e) => {
            this.ButtonLogin.IsEnabled = false;
            await this.LogPortal();
            this.ButtonLogin.IsEnabled = true;
        };
    }
    private async Task LogPortal() {
        // Construct AGOL login url
        string query = string.Empty;
        query += string.Format("client_id={0}", AGOL_APPID);
        query += string.Format("&response_type={0}", "code");
        query += string.Format("&redirect_uri={0}", Uri.EscapeDataString(AGOL_REDIR));

        // Build url
        UriBuilder b = new UriBuilder(WelcomePage.AGOL_OAUTH) {
            Query = query
        };

        // Display 0Auth2 dialog
        WebAuthenticationResult w = await WebAuthenticationBroker.AuthenticateAsync(
            WebAuthenticationOptions.None,
            b.Uri,
            new Uri(AGOL_REDIR)
        );
        switch (w.ResponseStatus) {
            case WebAuthenticationStatus.Success:
                // Response ok?
                if (w.ResponseData == null) { return; }

                // Parse return uri
                Uri call = null;
                if (!Uri.TryCreate(w.ResponseData, UriKind.RelativeOrAbsolute, out call)) { return; }

                // Extract code
                string[] parameters = call.Query.TrimStart('?').Split('&');
                Dictionary<string, string> dict = new Dictionary<string, string>();
                foreach (string parameter in parameters) {
                    string[] parts = parameter.Split('=');
                    if (parts.Length != 2) { continue; }
                    dict.Add(parts[0], parts[1]);
                }
                var item = dict.FirstOrDefault(kvp => kvp.Key == "code");
                string code = item.Value;
                if (string.IsNullOrWhiteSpace(code)) { return; }

                // Build token request query
                string query2 = string.Empty;
                query2 += string.Format("client_id={0}", AGOL_APPID);
                query2 += string.Format("&grant_type={0}", "authorization_code");
                query2 += string.Format("&code={0}", code);
                query2 += string.Format("&redirect_uri={0}", Uri.EscapeDataString(AGOL_REDIR));

                // Build token request url
                UriBuilder builder = new UriBuilder(WelcomePage.AGOL_GETTK) {
                    Query = query2
                };

                // Get response
                HttpClient c = new HttpClient();
                string text = await c.GetStringAsync(builder.Uri);
                if (string.IsNullOrWhiteSpace(text)) { return; }

                // Parse json - extract token
                JsonValue jv = null;
                if (!JsonValue.TryParse(text, out jv)) { return; }
                JsonObject jo = jv.GetObject();
                string access = jo.GetNamedString("access_token");
                double expire = jo.GetNamedNumber("expires_in");
                string refresh = jo.GetNamedString("refresh_token");
                string username = jo.GetNamedString("username");

                // Instantiate portal
                ArcGISPortal portal = await ArcGISPortal.CreateAsync(null, null, access);

                // Store reference to portal
                QuizDataSource.Default.Portal = portal;

                // Navigate to main page
                this.Frame.Navigate(typeof(MainPage));

                break;
            case WebAuthenticationStatus.ErrorHttp:
                string http_errorcode = w.ResponseErrorDetail.ToString();
                this.GridWelcome.Visibility = Visibility.Visible;
                this.ErrorControl.Show(http_errorcode);
                break;
            case WebAuthenticationStatus.UserCancel:
            default:
                break;
        }
    }
}

Base93 - Integer Shortening in C#

base93

This post will describe a technique of shortening an integer by approximately 50%.  For example, the approximate coordinate extent of continental United States in web Mercator can be expressed in the following JSON representation.
{"xmin":-15438951,"ymin":1852835,"xmax":-5420194,"ymax":7485635},

…or simplified to:
-15438951,1852835,-5420194,7485635

…or shortened to:
-ji5l,2sk},cGyR,70o#

The principle being employed here is that numbers are normally expressed using the decimal or base ten numerical system.  This system requires ten characters (or “digits”), 0 to 9, to formulate a number.  However, because ASCII has 95 printable characters we can encode numbers using base 93 which can result in printed strings that are 50% shorter than their decimal cousins.  For example:

-15438951 to –ji5l, or
1852835 to 2sk}

Why not use all 128 ASCII characters?  Well. 33 are non-printable and the characters “,” and “-“ are reserved for delimiter and negation respectively.

Lastly, to reduce the overall length of extents, the xmax is expressed as a width and ymax as a height.  At small scales the benefit of this technique is negligible (if any) but significant at large scales.

Please see below for source code and test method.

using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;

namespace ESRI.PrototypeLab.Base93 {
    public static class Base93Extension {
        private const string CHRS =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +
" !?\"'`^#$%@&*+=/.:;|\\_<>[]{}()~"; public static string ToShortenedString(this int num) { string result = string.Empty; if (num < 0) { result += "-"; num = Math.Abs(num); } int len = CHRS.Length; int index = 0; while (num >= Math.Pow(len, index)) { index++; } index--; while (index >= 0) { int pow = (int)Math.Pow(len, index); int div = num / pow; result += CHRS[div]; num -= pow * div; index--; } return result; } public static int ToUncompressedInteger(this string s) { int result = 0; int len = CHRS.Length; int index = 0; var chars = s.ToCharArray(); foreach (char c in chars.Reverse().Where(x => x != '-')) { int pow = (int)Math.Pow(len, index); int ind = CHRS.IndexOf(c); result += pow * ind; index++; } if (chars.First() == '-') { result *= -1; } return result; } internal static void Test() { int max = int.MaxValue; string com = max.ToShortenedString(); int unc = com.ToUncompressedInteger(); double x = (double)com.Length /
(double)max.ToString(CultureInfo.InvariantCulture).Length; Debug.WriteLine("Max Integer: {0}", max); Debug.WriteLine("Compressed: {0}", com); Debug.WriteLine("Uncompressed: {0}", unc); Debug.WriteLine("Compression: {0:P}", 1 - x); } } }

Thursday, June 7, 2012

How to programmatically add hyperlinks to a Silverlight RichTextBox

Sir Edmund Percival Hillary

There are many examples demonstrating how to embed hyperlinks into a Silverlight RichTextBox, but they all tend to be for design-time use.  This post discusses a technique for adding hyperlinks dynamically at runtime.

To begin, the following RichTextBox contains text from a Wikipedia article about Sir Edmund Hillary.

<UserControl

    x:Class="SilverlightApplication12.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    mc:Ignorable="d"

    d:DesignHeight="300"

    d:DesignWidth="400"

    >

    <Grid>

        <RichTextBox

            x:Name="rtb"

            Margin="10,10,10,10"

            IsReadOnly="True"

            FontFamily="Georgia"

            FontSize="16"

            TextAlignment="Justify"

            BorderThickness="0"

            >

            <Paragraph>

                <Run

                    FontStyle="Italic"

                    Text="&quot;Sir Edmund Percival Hillary, KG, ONZ, KBE (20 July 1919 – 11 January 2008) was a New Zealand mountaineer, explorer and philanthropist. On 29 May 1953, Hillary and Nepalese Sherpa mountaineer Tenzing Norgay became the first climbers confirmed as having reached the summit of Mount Everest – see Timeline of climbing Mount Everest. They were part of the ninth British expedition to Everest, led by John Hunt. He was named by Time magazine as one of the 100 most influential people of the 20th century.&quot;"

                    />

                <LineBreak />

                <LineBreak />

                <Run Text="from Edmund Hillary/Wikipedia"/>

                <LineBreak />

            </Paragraph>

        </RichTextBox>

    </Grid>

</UserControl>

Below, the developer is calling a custom extension method called “hyperlink” to add links to other Wikipedia articles. The key capability here is that hyperlinks are being added programmatically at runtime.

public partial class MainPage : UserControl {

    public MainPage() {

        InitializeComponent();

 

        this.rtb.Hyperlink("KG",
            "http://en.wikipedia.org/wiki/Order_of_the_Garter");

        this.rtb.Hyperlink("ONZ",
            "http://en.wikipedia.org/wiki/Order_of_New_Zealand");

        this.rtb.Hyperlink("KBE",
            "http://en.wikipedia.org/wiki/Order_of_the_British_Empire");

        this.rtb.Hyperlink("mountaineer",
            "http://en.wikipedia.org/wiki/Mountaineering");

        this.rtb.Hyperlink("Nepalese", "http://en.wikipedia.org/wiki/Nepal");

        this.rtb.Hyperlink("Sherpa", "http://en.wikipedia.org/wiki/Sherpa_people");

        this.rtb.Hyperlink("Tenzing Norgay",
            "http://en.wikipedia.org/wiki/Tenzing_Norgay");

        this.rtb.Hyperlink("Mount Everest",
            "http://en.wikipedia.org/wiki/Mount_Everest");

        this.rtb.Hyperlink("Timeline of climbing Mount Everest",
            "http://en.wikipedia.org/wiki/Timeline_of_climbing_Mount_Everest");

        this.rtb.Hyperlink("John Hunt",
            "http://en.wikipedia.org/wiki/John_Hunt,_Baron_Hunt");

        this.rtb.Hyperlink("100 most influential",
"http://en.wikipedia.org/wiki/Time_100:_The_Most_Important_People_of_the_Century");

        this.rtb.Hyperlink("Edmund Hillary",
           
"http://en.wikipedia.org/wiki/Sir_Edmund_Hillary");

        this.rtb.Hyperlink("Wikipedia", "http://en.wikipedia.org");

    }

}

 

Below is the code that defines the "hyperlink” extension method used above.  After considerable research, I discovered that the best way to insert a hyperlink at runtime is to programmatically select text and then insert a hyperlink with the insert method.  The complication was, how to get a TextPointer  for the linked text?  Based on logic in last year’s article, I iterated through every Paragraph and Run until the searched text was found.  When the host Run was located, a TextPointer were extracted using the ContentStart and ContentEnd properties.

 

public static class Extensions {

    public static void Hyperlink(this RichTextBox rtb, string text, string link) {

        // Argument Checking

        if (string.IsNullOrWhiteSpace(text)) {

            throw new ArgumentNullException("text");

        }

        if (string.IsNullOrWhiteSpace(link)) {

            throw new ArgumentNullException("link");

        }

        if (!Uri.IsWellFormedUriString(link, UriKind.RelativeOrAbsolute)) {

            throw new ArgumentException("link is not a valid url");

        }

 

        foreach (Block block in rtb.Blocks) {

            Paragraph paragraph = block as Paragraph;

            if (paragraph == null){continue;}

            foreach (Inline inline in paragraph.Inlines) {

                Run run = inline as Run;

                if (run == null) { continue; }

                int index = run.Text.IndexOf(text);

                if (index == -1) { continue; }

 

                TextPointer start = run.ContentStart.GetPositionAtOffset(
                    index,
LogicalDirection.Forward);

                TextPointer end = run.ContentStart.GetPositionAtOffset(
                    index + text.Length,
LogicalDirection.Forward);

                rtb.Selection.Select(start, end);

 

                Hyperlink hyperlink = new Hyperlink() {

                    NavigateUri = new Uri(link),

                    TargetName = "_blank"

                };

                hyperlink.Inlines.Add(text);

                rtb.Selection.Insert(hyperlink);

                return;

            }

        }

    }

}

Silverlight’s RichTextBox is a very powerful control for rendering text with complicated formatting. This code makes it a little more powerful by adding the ability to dynamically add hyperlinks.

Friday, April 27, 2012

How to Encrypt/Decrypt Text in Silverlight/.NET

image

As web or application developers it is advisable to never transmit or store passwords as plain text. If unencrypted, a person could intentionally (or unintentionally) discover somebody else’s password. The technique described in this post is certainly not 100% secure but it makes password cracking difficult. To obtain a person’s original password the hacker must have the encrypted password and the key embedded in the client application.

The code included below will work in both Silverlight and .NET. An Advanced Encryption Standard (AES) symmetric algorithm is used to scramble/descramble text as shown in the screenshot above.

using System;

using System.IO;

using System.Security.Cryptography;

using System.Text;

using System.Windows;

 

namespace TestPasswordEncryption {

    public partial class MainWindow : Window {

        public MainWindow() {

            InitializeComponent();

 

            // Encrypt Text

            this.ButtonEncrypt.Click += (s, e) => {

                this.TextBoxEncrypted.Text =
                   
Scrambler.Encrypt(this.TextBoxOriginal.Text);

            };

 

            // Decrypt Text

            this.ButtonDecrypt.Click += (s, e) => {

                this.TextBoxDecrypted.Text =
                   
Scrambler.Decrypt(this.TextBoxEncrypted.Text);

            };

        }

    }

 

    internal class Scrambler {

        private const string PASS = "6D114A94-375E-4FFD-9F48-CF2C7A12620F";

        private const string SALT = "E5A8DB1B-5EAB-4C62-B322-CE24CE274303";

 

        internal static string Encrypt(string input) {

            // Test data

            byte[] utfdata = UTF8Encoding.UTF8.GetBytes(input);

            byte[] saltBytes = UTF8Encoding.UTF8.GetBytes(Scrambler.SALT);

 

            // We're using the PBKDF2 standard for password-based key generation

            Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(

                Scrambler.PASS, saltBytes, 1000);

 

            // Our symmetric encryption algorithm

            AesManaged aes = new AesManaged();

            aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;

            aes.KeySize = aes.LegalKeySizes[0].MaxSize;

            aes.Key = rfc.GetBytes(aes.KeySize / 8);

            aes.IV = rfc.GetBytes(aes.BlockSize / 8);

 

            // Encryption

            ICryptoTransform encryptTransf = aes.CreateEncryptor();

 

            // Output stream, can be also a FileStream

            MemoryStream encryptStream = new MemoryStream();

            CryptoStream encryptor = new CryptoStream(

                encryptStream, encryptTransf, CryptoStreamMode.Write);

            encryptor.Write(utfdata, 0, utfdata.Length);

            encryptor.Flush();

            encryptor.Close();

 

            // Showing our encrypted content

            byte[] encryptBytes = encryptStream.ToArray();

            string encryptedString = Convert.ToBase64String(encryptBytes);

 

            // Close stream

            encryptStream.Close();

 

            // Return encrypted text

            return encryptedString;

        }

        internal static string Decrypt(string base64Input) {

            // Get inputs as bytes

            byte[] encryptBytes = Convert.FromBase64String(base64Input);

            byte[] saltBytes = Encoding.UTF8.GetBytes(Scrambler.SALT);

 

            // We're using the PBKDF2 standard for password-based key generation

            Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(

                Scrambler.PASS, saltBytes);

 

            // Our symmetric encryption algorithm

            AesManaged aes = new AesManaged();

            aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;

            aes.KeySize = aes.LegalKeySizes[0].MaxSize;

            aes.Key = rfc.GetBytes(aes.KeySize / 8);

            aes.IV = rfc.GetBytes(aes.BlockSize / 8);

 

            // Now, decryption

            ICryptoTransform decryptTrans = aes.CreateDecryptor();

 

            // Output stream, can be also a FileStream

            MemoryStream decryptStream = new MemoryStream();

            CryptoStream decryptor = new CryptoStream(

                decryptStream, decryptTrans, CryptoStreamMode.Write);

            decryptor.Write(encryptBytes, 0, encryptBytes.Length);

            decryptor.Flush();

            decryptor.Close();

 

            // Showing our decrypted content

            byte[] decryptBytes = decryptStream.ToArray();

            string decryptedString = UTF8Encoding.UTF8.GetString(

                decryptBytes, 0, decryptBytes.Length);

 

            // Close Stream

            decryptStream.Close();

 

            // Return decrypted text

            return decryptedString;

        }

    }

}