Skip to Content
Mobile

Afaria API 201 - Build Your Own Enrollment Portal - Get an Activation ID and MDM-First Enrollment Link

Tags:

Introduction

This is the first in a series of documents on creating your own Enrollment Portal or End-User Self-Service Portal. It is meant as a continuation of our previous effort in Afaria API 101 and assumes completion of that section. We will learn here how to create a simple ASP.NET webpage which will accept user input, create a unique Activation ID (part of the "long enrollment code"), retrieve the iOS MDM-first enrollment URL, and present to the user for enrollment. Variable prompts for enrollment will be handled by the Enrollment Server.

Requirements and Assumptions

Table of Contents

Getting Started with the Project

This section is review from the previous section, Afaria API 101 - Consuming the Afaria API Services and Creating a Proof of Concept Application. As such, commenting is sparse. For more information, please review Afaria API 101.

  1. Create a new Project in Visual Studio. This example will be using a "ASP.NET Empty Web Application" and will rely on IIS for authentication, using Windows Authentication.
  2. Create the Service Reference, pointing to "http://<IPAddress>:7980/AfariaService/Policy" for the address, and named AfariaPolicy.
  3. Right-click on the Project in Solution Explorer > Add > New Item...
  4. Create a new Web Form and name it index.aspx.
  5. Double click on index.aspx to open it.
  6. Between the <div> tags, drag two Labels and two TextBoxes from the Toolbox into the index.aspx source page, naming them LabelUserName, TextBoxUserName, LabelEmailAddress, TextBoxEmailAddress respectively. Change the LabelUserName Text property to "UserName:" and the LabelEmailAddress Text property to "Email Address:".

    <asp:Label ID="LabelUserName" runat="server" Text="UserName:"></asp:Label>

    <asp:TextBox ID="TextBoxUserName" runat="server"></asp:TextBox>

    <asp:Label ID="LabelEmailAddress" runat="server" Text="Email Address:"></asp:Label>

    <asp:TextBox ID="TextBoxEmailAddress" runat="server"></asp:TextBox>

  7. Add another pair of div tags and drag a button from the Toolbox to the index.aspx source page. Name it ButtonGetCode, set the Text to "Get Code". Next, click on the Split button to show the Design and Source views simultaneously, then double-click on the button to create the OnClick event automatically.

    <div><asp:Button ID="ButtonGetCode" runat="server" Text="Get Code" OnClick="ButtonGetCode_Click" /></div>

  8. Switch back to index.aspx and add another pair of div tags and drag a HyperLink from the Toolbox to the index.aspx source page. Name it HyperLinkMDMFirstUrl, then right-click on the hyperlink in the Design view and select Properties. Set the properties for Enabled to False and Visible to False.

    <div><asp:HyperLink ID="HyperLinkMDMFirstUrl" runat="server" Enabled="False" Visible="False">HyperLink</asp:HyperLink></div>

  9. Switch back to index.aspx.cs, add a using directive for the service reference created in step 2 (e.g.: using AfariaAPI201_SSP.AfariaPolicy;) . Create a class named AfariaHelper. In AfariaHelper, add a public static method named getClientTypes that takes no parameters, returns List<string>, and iterates through the AfariaPolicy.ClientType enumeration, adding to the list to be returned.

            public static List<string> getClientTypes()

            {

                List<string> types = new List<string>();

                foreach (string type in Enum.GetNames(typeof(ClientType)))

                {

                    if (type != "All" && type != "Undefined")

                    {

                        types.Add(type);

                    }

                }

                return types;

            }

  10. Switch back to index.aspx and drag an ObjectDataSource from the Data section of the Toolbox to the index.aspx source page, below the DropDownListClientType. Change the ID property to ObjectDataSourceClientType.
  11. Press the F6 button to build the solution so the method created previously will be available as a data source.
  12. Right-click on the new ObjectDataSourceClientType in the Design pane and select Configure Data Source... to get the wizard. In the first page, select the AfariaHelper class. If no objects are shown in the dropdown, assure that "Show only data components" is not selected and that you have run the build without running into errors. Click Next.
  13. In the Select tab, choose getClientTypes() and select Finish. After doing this, the line in index.aspx should look similar to below.

    <asp:ObjectDataSource ID="ObjectDataSourceClientType" runat="server" SelectMethod="getClientTypes" TypeName="AfariaAPI201_SSP.AfariaHelper"></asp:ObjectDataSource>

  14. Go to the properties for DropDownListClientType, and change the DataSourceID to ObjectDataSourceClientType.

    <asp:DropDownList ID="DropDownListClientType" runat="server" DataSourceID="ObjectDataSourceClientType"></asp:DropDownList>

  15. Save all files.

Initializing/Connecting to the API and Closing Properly

This section is review from the previous section, Afaria API 101 - Consuming the Afaria API Services and Creating a Proof of Concept Application. As such, this is just a code sample. For more information, please review Afaria API 101.

Notes regarding the example

  • The isPolicyServiceOperable method is meant as a check on whether the service exists and is not faulted, and is derived from the cleanup example provided in API 101.
  • The contextCount will be a counter to keep track of the services using the context associated with the Helper object. Since we are only using the one service in this session, it is not necessary, but will be used in later sessions.
  • This example will implement IDisposable, so the public and protected Dispose are necessary. For more information, please consult MSDN documentation.

public class AfariaHelper : IDisposable

{

    public string contextID { get; private set; }

    int contextCount;

    string APIdomain, APIaccount, APIpassword, APIaddress;

    PolicyServiceClient svcPolicy;

    bool disposed = false;

    int TenantID;

#region Static Methods

    public static List<string> getClientTypes()

    {

          List<string> types = new List<string>();

          foreach (string type in Enum.GetNames(typeof(ClientType)))

          {

              if (type != "All" && type != "Undefined")

              {

                    types.Add(type);

              }

          }

          return types;

    }

#endregion Statics

#region Public Instance Methods & Constructor/Destructor

    public AfariaHelper(string APIdomain, string APIaccount, string APIpassword, string APIaddress = "127.0.0.1", int TenantID = 0)

    {

          this.APIdomain = APIdomain;

          this.APIaccount = APIaccount;

          this.APIpassword = APIpassword;

          this.APIaddress = APIaddress;

          this.TenantID = TenantID;

          contextID = Guid.NewGuid().ToString();

          contextCount = 0;

          initPolicyService();

    }

    public void Dispose()

    {

          Dispose(true);

    }

    protected void Dispose(bool disposing)

    {

          if (disposed)

          {

              return;

          }

          if (disposing)

          {

              contextCleanup();

              if (svcPolicy != null)

              {

                    svcPolicy.Close();

                    svcPolicy = null;

              }

          }

          disposed = true;

    }

#endregion Public Instance Methods & Constructor/Destructor

#region Protected/Private Instance Methods

    protected void initPolicyService()

    {

          svcPolicy = new PolicyServiceClient("NetTcpBinding_IPolicyService", "net.tcp://" + APIaddress + ":7982/AfariaService/Policy");

          svcPolicy.ClientCredentials.Windows.ClientCredential.Domain = APIdomain;

          svcPolicy.ClientCredentials.Windows.ClientCredential.UserName = APIaccount;

          svcPolicy.ClientCredentials.Windows.ClientCredential.Password = APIpassword;

          ContextInfo ci = svcPolicy.InitContext(contextID);

          svcPolicy.SetTenantIdContext(TenantID);

          contextCount++;

    }

    protected bool isPolicyServiceOperable()

    {

          bool ret = false;

          if (svcPolicy != null)

          {

              if (svcPolicy.State == System.ServiceModel.CommunicationState.Faulted)

              {

                    svcPolicy.Abort();

                    contextCleanup();

                    svcPolicy = null;

                    try

                    {

                        initPolicyService();

                        ret = true;

                    }

                    catch (Exception ex)

                    {

                        throw new Exception("Error initializing service in isPolicyServiceOperable.", ex);

                    }

              }

              else if (svcPolicy.State == System.ServiceModel.CommunicationState.Opened)

              {

                    ret = true;

              }

          }

          return ret;

    }

    protected bool contextCleanup()

    {

          if (contextCount > 1)

          {

              contextCount--;

          }

          else if (contextCount == 1 || contextCount == 0)

          {

              PolicyServiceClient svcTemp = new PolicyServiceClient("NetTcpBinding_IPolicyService", "net.tcp://" + APIaddress + ":7982/AfariaService/Policy");

              svcTemp.ClientCredentials.Windows.ClientCredential.Domain = APIdomain;

              svcTemp.ClientCredentials.Windows.ClientCredential.UserName = APIaccount;

              svcTemp.ClientCredentials.Windows.ClientCredential.Password = APIpassword;

              svcTemp.InitContext(contextID);

              svcTemp.CloseContext();

              contextCount = 0;

              return true;

          }

          else

          {

              throw new Exception("We've lost track of our contexts!");

          }

          return false;

    }

#endregion Protected/Private Instance Methods

}

Getting the Activation ID

The Activation ID is the part of the "long enrollment code" which ties the enrollment portal (End User Self-Service Portal) user to the device which is enrolling. It is a set of characters which are generated in a random fashion by the Afaria Server.

Get the EnrollmentCodeActivationIdInfo object which contains the Activation ID by calling EnrollmentCodeGenerateActivationIdWithAddress, specifying the user account and email address of the enrolling user, along with the short enrollment code and a client type. Since this example is for iOS enrollment, we will specify ClienType.Ios, but we will take that as a parameter, so that other device types can also use this method.

For "client-first" enrollment, the string output of this function can be entered by the user in the client, and the user will be tied to the submitted userAccount. The user can either enter the code manually, or the code can be presented to the user as a link which will populate the client if it has already been opened. By way of example, for the enrollment code "theioscode", present it to the user as the link "afaria://e=theioscode". This will work for both the enrollment code, and the "long enrollment code".

public string getActivationID(string userAccount, string enrollCode, string userEmail, ClientType deviceClientType)

{

    string output = string.Empty;

    if (isPolicyServiceOperable())

    {

          EnrollmentCodeActivationIdInfo activationID = svcPolicy.EnrollmentCodeGenerateActivationIdWithAddress(userAccount, enrollCode, userEmail, deviceClientType);

          output = activationID.CompleteActivationId;

    }

    else

    {

          throw new Exception("Policy Service is not operable");

    }

    return output;

}

Getting the User Specific MDM-First Enrollment URL

To access the MDM-first enrollment url, you must retrieve the policy settings for a specific Enrollment Policy. This can be retrieved by calling OpenPolicy with the enrollment code specified, and then by calling EnrollmentGetPolicySettings with the DataState object returned by OpenPolicy. The URL is a member of the EnrollmentPolicySettings, but only contains the "short" enrollment code, so to tie the enrollment to the user, it is necessary to replace the enrollment code in the URL with the "long enrollment code". Of course, to get the "long enrollment code", we must call the method discussed above, getActivationID.

The DataState object is an object which represents state information which needs to be persisted across multiple interface calls. It is especially common when calling API functions which have the possibility to return a lot of information, and therefore present API calls to page through the results. It is necessary here for retrieving the policy settings for the Enrollment Policy.

public string getEnrollmentUrl(string userAccount, string enrollCode, string userEmail)

{

    string url = string.Empty;

    string activationID = string.Empty;

    activationID = getActivationID(userAccount, enrollCode, userEmail, ClientType.Ios);

    if(!(string.IsNullOrEmpty(activationID)) && (isPolicyServiceOperable()))

    {

          AfariaPolicy.DataState ds = svcPolicy.OpenPolicy(enrollCode);

          EnrollmentPolicySettingsIos plcySettings = (EnrollmentPolicySettingsIos)svcPolicy.EnrollmentGetPolicySettings(ds);

          svcPolicy.Release(ds);

          if (plcySettings != null)

          {

              url = plcySettings.MdmEnrollmentUrl;

              url = url.Replace(enrollCode, activationID.Replace(" ", string.Empty));

          }

          else

          {

              throw new Exception("Specified enrollment code is invalid.");

          }

          return url;

    }

    else

    {

          throw new Exception("Policy Service is not operable");

    }

}

Full Example Program

  • Index.aspx.cs

    This document only covers the AfariaHelper class, and not the index class as all code that interacts with the API is contained in the AfariaHelper. AfariaHelper was covered in it's entirety in previous sections of this document.

    Note the following about the index class:

    -Since this session covers only iOS enrollment, iOS is preselected and user interaction with the device type dropdown is disabled on page load.

    -ButtonGetCode_Click first instantiates an AfariaHelper, passing the server info and service account info. After doing this, it makes a call into AfariaHelper's getEnrollmentUrl function, passing the user entered items and enrollment code.

    -getConfigSetting pulls any specified setting from the web.config file, so that values do not need to be hard-coded and can be changed without re-building the project. These settings specified by the string parameters must be set up in the web.config, by specifying keys for each under an appSettings section as shown below.

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Web;

    using System.Web.UI;

    using System.Web.UI.WebControls;

    using AfariaAPI201.AfariaPolicy;

    namespace AfariaAPI201

    {

        public partial class index : System.Web.UI.Page

        {

            protected void Page_Load(object sender, EventArgs e)

            {

                DropDownListClientType.Enabled = false;

                DropDownListClientType.SelectedIndex = 2;

            }

            protected void ButtonGetCode_Click(object sender, EventArgs e)

            {

                try

                {

                    using (AfariaHelper apiHelper = new AfariaHelper(getConfigSetting("AfariaAPIServiceAccountDomain")

                                                                    , getConfigSetting("AfariaAPIServiceAccountUsername")

                                                                    , getConfigSetting("AfariaAPIServiceAccountPassword")

                                                                    , getConfigSetting("AfariaAPIServiceAddress")))

                    {

                        HyperLinkMDMFirstUrl.NavigateUrl = apiHelper.getEnrollmentUrl(TextBoxUserName.Text

                                                                                , getConfigSetting("AfariaIOSEnrollmentCode")

                                                                                , TextBoxEmailAddress.Text);

                    }

                    HyperLinkMDMFirstUrl.Text = HyperLinkMDMFirstUrl.NavigateUrl;

                    HyperLinkMDMFirstUrl.Enabled = true;

                    HyperLinkMDMFirstUrl.Visible = true;

                }

                catch (Exception ex)

                {

                    System.Diagnostics.Trace.WriteLine(ex.Message);

                    System.Diagnostics.Trace.WriteLine(ex.StackTrace);

                }

            }

            private string getConfigSetting(string setting)

            {

                string value = string.Empty;

                if (System.Configuration.ConfigurationManager.AppSettings.Count > 0)

                {

                    value = System.Configuration.ConfigurationManager.AppSettings[setting];

                }

                return value;

            }

        }

        public class AfariaHelper : IDisposable

        {

            public string contextID { get; private set; }

            int contextCount;

            string APIdomain, APIaccount, APIpassword, APIaddress;

            PolicyServiceClient svcPolicy;

            bool disposed = false;

            int TenantID;

    #region Static Methods

            public static List<string> getClientTypes()

            {

                List<string> types = new List<string>();

                foreach (string type in Enum.GetNames(typeof(ClientType)))

                {

                    if (type != "All" && type != "Undefined")

                    {

                        types.Add(type);

                    }

                }

                return types;

            }

    #endregion Statics

    #region Public Instance Methods & Constructor/Destructor

            public AfariaHelper(string APIdomain, string APIaccount, string APIpassword, string APIaddress = "127.0.0.1", int TenantID = 0)

            {

                this.APIdomain = APIdomain;

                this.APIaccount = APIaccount;

                this.APIpassword = APIpassword;

                this.APIaddress = APIaddress;

                this.TenantID = TenantID;

                contextID = Guid.NewGuid().ToString();

                contextCount = 0;

                initPolicyService();

            }

            public void Dispose()

            {

                Dispose(true);

            }

            protected void Dispose(bool disposing)

            {

                if (disposed)

                {

                    return;

                }

                if (disposing)

                {

                    contextCleanup();

                    if (svcPolicy != null)

                    {

                        svcPolicy.Close();

                        svcPolicy = null;

                    }

                }

                disposed = true;

            }

            public string getEnrollmentUrl(string userAccount, string enrollCode, string userEmail)

            {

                string url = string.Empty;

                string activationID = string.Empty;

                activationID = getActivationID(userAccount, enrollCode, userEmail, ClientType.Ios);

                if (!(string.IsNullOrEmpty(activationID)) && (isPolicyServiceOperable()))

                {

                    AfariaPolicy.DataState ds = svcPolicy.OpenPolicy(enrollCode);

                    EnrollmentPolicySettingsIos plcySettings = (EnrollmentPolicySettingsIos)svcPolicy.EnrollmentGetPolicySettings(ds);

                    svcPolicy.Release(ds);

                    if (plcySettings != null)

                    {

                        url = plcySettings.MdmEnrollmentUrl;

                        url = url.Replace(enrollCode, activationID.Replace(" ", string.Empty));

                    }

                    else

                    {

                        throw new Exception("Specified enrollment code is invalid.");

                    }

                    return url;

                }

                else

                {

                    throw new Exception("Policy Service is not operable");

                }

            }

            public string getActivationID(string userAccount, string enrollCode, string userEmail, ClientType deviceClientType)

            {

                string output = string.Empty;

                if (isPolicyServiceOperable())

                {

                    EnrollmentCodeActivationIdInfo activationID = svcPolicy.EnrollmentCodeGenerateActivationIdWithAddress(userAccount, enrollCode, userEmail, deviceClientType);

                    output = activationID.CompleteActivationId;

                }

                else

                {

                    throw new Exception("Policy Service is not operable");

                }

                return output;

            }

    #endregion Public Instance Methods & Constructor/Destructor

    #region Protected/Private Instance Methods

            protected void initPolicyService()

            {

                svcPolicy = new PolicyServiceClient("NetTcpBinding_IPolicyService", "net.tcp://" + APIaddress + ":7982/AfariaService/Policy");

                svcPolicy.ClientCredentials.Windows.ClientCredential.Domain = APIdomain;

                svcPolicy.ClientCredentials.Windows.ClientCredential.UserName = APIaccount;

                svcPolicy.ClientCredentials.Windows.ClientCredential.Password = APIpassword;

                ContextInfo ci = svcPolicy.InitContext(contextID);

                svcPolicy.SetTenantIdContext(TenantID);

                contextCount++;

            }

            protected bool isPolicyServiceOperable()

            {

                bool ret = false;

                if (svcPolicy != null)

                {

                    if (svcPolicy.State == System.ServiceModel.CommunicationState.Faulted)

                    {

                        svcPolicy.Abort();

                        contextCleanup();

                        svcPolicy = null;

                        try

                        {

                            initPolicyService();

                            ret = true;

                        }

                        catch (Exception ex)

                        {

                            throw new Exception("Error initializing service in isPolicyServiceOperable.", ex);

                        }

                    }

                    else if (svcPolicy.State == System.ServiceModel.CommunicationState.Opened)

                    {

                        ret = true;

                    }

                }

                return ret;

            }

            protected bool contextCleanup()

            {

                if (contextCount > 1)

                {

                    contextCount--;

                }

                else if (contextCount == 1 || contextCount == 0)

                {

                    PolicyServiceClient svcTemp = new PolicyServiceClient("NetTcpBinding_IPolicyService", "net.tcp://" + APIaddress + ":7982/AfariaService/Policy");

                    svcTemp.ClientCredentials.Windows.ClientCredential.Domain = APIdomain;

                    svcTemp.ClientCredentials.Windows.ClientCredential.UserName = APIaccount;

                    svcTemp.ClientCredentials.Windows.ClientCredential.Password = APIpassword;

                    svcTemp.InitContext(contextID);

                    svcTemp.CloseContext();

                    contextCount = 0;

                    return true;

                }

                else

                {

                    throw new Exception("We've lost track of our contexts!");

                }

                return false;

            }

    #endregion Protected/Private Instance Methods

        }

    }

  • Index.aspx

    Creation of this file was covered in Section 1 of this document. Please ignore the <span> tags in the example, if present.

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="index.aspx.cs" Inherits="AfariaAPI201.index" %>

    <!DOCTYPE html>

    <html xmlns="http://www.w3.org/1999/xhtml">

    <head runat="server">

        <title>AfariaAPI-SSP</title>

    </head>

    <body>

        <form id="form1" runat="server">

        <div>

            <asp:Label ID="LabelUserName" runat="server" Text="UserName:"></asp:Label>

            <asp:TextBox ID="TextBoxUserName" runat="server"></asp:TextBox>

            <asp:Label ID="LabelEmailAddress" runat="server" Text="Email Address:"></asp:Label>

            <asp:TextBox ID="TextBoxEmailAddress" runat="server"></asp:TextBox>

            <asp:DropDownList ID="DropDownListClientType" runat="server" DataSourceID="ObjectDataSourceClientType"></asp:DropDownList>

            <asp:ObjectDataSource ID="ObjectDataSourceClientType" runat="server" SelectMethod="getClientTypes" TypeName="AfariaAPI201.AfariaHelper"></asp:ObjectDataSource>

        </div>

        <div><asp:Button ID="ButtonGetCode" runat="server" Text="Get Code" OnClick="ButtonGetCode_Click" /></div>

        <div><asp:HyperLink ID="HyperLinkMDMFirstUrl" runat="server" Enabled="False" Visible="False">HyperLink</asp:HyperLink></div>

        </form>

    </body>

    </html>

  • web.config appSettings section

    The values here should be replaced with values relevant to your Afaria environment.

    <appSettings>

        <add key="AfariaAPIServiceAccountDomain" value="<AfariaAPIServiceAccountDomain>"/>

        <add key="AfariaAPIServiceAccountUsername" value="<AfariaAPIServiceAccountUserName>"/>

        <add key="AfariaAPIServiceAccountPassword" value="<AfariaAPIServiceAccountPassword>"/>

        <add key="AfariaAPIServiceAddress" value="<AfariaAPIServiceIPorAddress>"/>

        <add key="AfariaIOSEnrollmentCode" value="<AfariaIOSEnrollmentCode>"/>

    </appSettings>