Helper code: DeviceIdManager class
Applies To: Dynamics 365 (online), Dynamics 365 (on-premises), Dynamics CRM 2016, Dynamics CRM Online
The DeviceIdManager class registers a computing device with Microsoft account, through the generation of a device ID and password, and optionally stores that information in an encrypted format on the local disk for later reuse. This functionality is required when authenticating with Microsoft Dynamics 365 (online) through the Microsoft account identity provider. The device registration is stored in the %USERPROFILE%\LiveDeviceID folder on the computing device.
The primary class method to call from your code is LoadOrRegisterDevice. For an example of how the DeviceIdManager class is used, see the GetDeviceCredentials method in the Helper code: ServerConnection class topic.
A Microsoft Azure hosted application that must authenticate with Microsoft Dynamics 365 (online) should either set the PersistToFile property of DeviceIdManager to false or call the two-parameter LoadOrRegisterDevice method passing in the required device ID and password. In both cases, the DeviceIdManager won’t attempt to save the device ID and password to a file.
Download the Microsoft Dynamics CRM SDK package. The source code for the class can be found in the following location in the download package:
SDK\SampleCode\CS\HelperCode\DeviceIdManager.cs
SDK\SampleCode\VB\HelperCode\DeviceIdManager.vb
Requirements
For more information about the requirements for running the sample code provided in this SDK, see Use the sample and helper code.
Demonstrates
The DeviceIdManager class demonstrates how to manage the registration of computing devices with Microsoft account.
Example
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Net;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.ServiceModel.Description;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace Microsoft.Crm.Services.Utility
{
/// <summary>
/// Management utility for the Device Id
/// </summary>
public static class DeviceIdManager
{
#region Fields
private static readonly Random RandomInstance = new Random();
public const int MaxDeviceNameLength = 24;
public const int MaxDevicePasswordLength = 24;
#endregion
#region Constructor
static DeviceIdManager()
{
PersistToFile = true;
}
#endregion
#region Properties
/// <summary>
/// Indicates whether the registered device credentials should be persisted to the database
/// </summary>
public static bool PersistToFile { get; set; }
/// <summary>
/// Indicates that the credentials should be persisted to the disk if registration fails with DeviceAlreadyExists.
/// </summary>
/// <remarks>
/// If the device already exists, there is a possibility that the credentials are the same as the current credentials that
/// are being registered. This is especially true in automated environments where the same credentials are used continually (to avoid
/// registering spurious device credentials.
/// </remarks>
public static bool PersistIfDeviceAlreadyExists { get; set; }
#endregion
#region Methods
/// <summary>
/// Loads the device credentials (if they exist).
/// </summary>
/// <returns></returns>
public static ClientCredentials LoadOrRegisterDevice()
{
return LoadOrRegisterDevice(null);
}
/// <summary>
/// Loads the device credentials (if they exist).
/// </summary>
/// <param name="deviceName">Device name that should be registered</param>
/// <param name="devicePassword">Device password that should be registered</param>
public static ClientCredentials LoadOrRegisterDevice(string deviceName, string devicePassword)
{
return LoadOrRegisterDevice(null, deviceName, devicePassword);
}
/// <summary>
/// Loads the device credentials (if they exist).
/// </summary>
/// <param name="issuerUri">URL for the current token issuer</param>
/// <remarks>
/// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
/// </remarks>
public static ClientCredentials LoadOrRegisterDevice(Uri issuerUri)
{
return LoadOrRegisterDevice(issuerUri, null, null);
}
/// <summary>
/// Loads the device credentials (if they exist).
/// </summary>
/// <param name="issuerUri">URL for the current token issuer</param>
/// <param name="deviceName">Device name that should be registered</param>
/// <param name="devicePassword">Device password that should be registered</param>
/// <remarks>
/// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
/// </remarks>
public static ClientCredentials LoadOrRegisterDevice(Uri issuerUri, string deviceName, string devicePassword)
{
ClientCredentials credentials = LoadDeviceCredentials(issuerUri);
if (null == credentials)
{
credentials = RegisterDevice(Guid.NewGuid(), issuerUri, deviceName, devicePassword);
}
return credentials;
}
/// <summary>
/// Registers the given device with Microsoft account with a random application ID
/// </summary>
/// <returns>ClientCredentials that were registered</returns>
public static ClientCredentials RegisterDevice()
{
return RegisterDevice(Guid.NewGuid());
}
/// <summary>
/// Registers the given device with Microsoft account
/// </summary>
/// <param name="applicationId">ID for the application</param>
/// <returns>ClientCredentials that were registered</returns>
public static ClientCredentials RegisterDevice(Guid applicationId)
{
return RegisterDevice(applicationId, (Uri)null);
}
/// <summary>
/// Registers the given device with Microsoft account
/// </summary>
/// <param name="applicationId">ID for the application</param>
/// <param name="issuerUri">URL for the current token issuer</param>
/// <returns>ClientCredentials that were registered</returns>
/// <remarks>
/// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
/// </remarks>
public static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri)
{
return RegisterDevice(applicationId, issuerUri, null, null);
}
/// <summary>
/// Registers the given device with Microsoft account
/// </summary>
/// <param name="applicationId">ID for the application</param>
/// <param name="deviceName">Device name that should be registered</param>
/// <param name="devicePassword">Device password that should be registered</param>
/// <returns>ClientCredentials that were registered</returns>
public static ClientCredentials RegisterDevice(Guid applicationId, string deviceName, string devicePassword)
{
return RegisterDevice(applicationId, (Uri)null, deviceName, devicePassword);
}
/// <summary>
/// Registers the given device with Microsoft account
/// </summary>
/// <param name="applicationId">ID for the application</param>
/// <param name="issuerUri">URL for the current token issuer</param>
/// <param name="deviceName">Device name that should be registered</param>
/// <param name="devicePassword">Device password that should be registered</param>
/// <returns>ClientCredentials that were registered</returns>
/// <remarks>
/// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
/// </remarks>
public static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri, string deviceName, string devicePassword)
{
if (string.IsNullOrEmpty(deviceName) && !PersistToFile)
{
throw new ArgumentNullException("deviceName", "If PersistToFile is false, then deviceName must be specified.");
}
else if (string.IsNullOrEmpty(deviceName) != string.IsNullOrEmpty(devicePassword))
{
throw new ArgumentNullException("deviceName", "Either deviceName/devicePassword should both be specified or they should be null.");
}
LiveDevice device = GenerateDevice(deviceName, devicePassword);
return RegisterDevice(applicationId, issuerUri, device);
}
/// <summary>
/// Loads the device's credentials from the file system
/// </summary>
/// <returns>Device Credentials (if set) or null</returns>
public static ClientCredentials LoadDeviceCredentials()
{
return LoadDeviceCredentials(null);
}
/// <summary>
/// Loads the device's credentials from the file system
/// </summary>
/// <param name="issuerUri">URL for the current token issuer</param>
/// <returns>Device Credentials (if set) or null</returns>
/// <remarks>
/// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
/// </remarks>
public static ClientCredentials LoadDeviceCredentials(Uri issuerUri)
{
//If the credentials should not be persisted to a file, then they won't be present on the disk.
if (!PersistToFile)
{
return null;
}
EnvironmentConfiguration environment = DiscoverEnvironmentInternal(issuerUri);
LiveDevice device = ReadExistingDevice(environment);
if (null == device || null == device.User)
{
return null;
}
return device.User.ToClientCredentials();
}
/// <summary>
/// Discovers the Microsoft account environment based on the Token Issuer
/// </summary>
public static string DiscoverEnvironment(Uri issuerUri)
{
return DiscoverEnvironmentInternal(issuerUri).Environment;
}
#endregion
#region Private Methods
private static EnvironmentConfiguration DiscoverEnvironmentInternal(Uri issuerUri)
{
if (null == issuerUri)
{
return new EnvironmentConfiguration(EnvironmentType.LiveDeviceID, "login.live.com", null);
}
Dictionary<EnvironmentType, string> searchList = new Dictionary<EnvironmentType, string>();
searchList.Add(EnvironmentType.LiveDeviceID, "login.live");
searchList.Add(EnvironmentType.OrgDeviceID, "login.microsoftonline");
foreach (KeyValuePair<EnvironmentType, string> searchPair in searchList)
{
if (issuerUri.Host.Length > searchPair.Value.Length &&
issuerUri.Host.StartsWith(searchPair.Value, StringComparison.OrdinalIgnoreCase))
{
string environment = issuerUri.Host.Substring(searchPair.Value.Length);
//Parse out the environment
if ('-' == environment[0])
{
int separatorIndex = environment.IndexOf('.', 1);
if (-1 != separatorIndex)
{
environment = environment.Substring(1, separatorIndex - 1);
}
else
{
environment = null;
}
}
else
{
environment = null;
}
return new EnvironmentConfiguration(searchPair.Key, issuerUri.Host, environment);
}
}
//In all other cases the environment is either not applicable or it is a production system
return new EnvironmentConfiguration(EnvironmentType.LiveDeviceID, issuerUri.Host, null);
}
private static void Serialize<T>(Stream stream, T value)
{
XmlSerializer serializer = new XmlSerializer(typeof(T), string.Empty);
XmlSerializerNamespaces xmlNamespaces = new XmlSerializerNamespaces();
xmlNamespaces.Add(string.Empty, string.Empty);
serializer.Serialize(stream, value, xmlNamespaces);
}
private static T Deserialize<T>(string operationName, Stream stream)
{
//Read the XML into memory so that the data can be used in an exception if necessary
using (StreamReader reader = new StreamReader(stream))
{
return Deserialize<T>(operationName, reader.ReadToEnd());
}
}
private static T Deserialize<T>(string operationName, string xml)
{
//Attempt to deserialize the data. If deserialization fails, include the XML in the exception that is thrown for further
//investigation
using (StringReader reader = new StringReader(xml))
{
try
{
XmlSerializer serializer = new XmlSerializer(typeof(T), string.Empty);
return (T)serializer.Deserialize(reader);
}
catch (InvalidOperationException ex)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"Unable to Deserialize XML (Operation = {0}):{1}{2}", operationName, Environment.NewLine, xml), ex);
}
}
}
private static FileInfo GetDeviceFile(EnvironmentConfiguration environment)
{
return new FileInfo(string.Format(CultureInfo.InvariantCulture, LiveIdConstants.FileNameFormat,
environment.Type,
string.IsNullOrEmpty(environment.Environment) ? null : "-" + environment.Environment.ToUpperInvariant()));
}
private static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri, LiveDevice device)
{
EnvironmentConfiguration environment = DiscoverEnvironmentInternal(issuerUri);
DeviceRegistrationRequest request = new DeviceRegistrationRequest(applicationId, device);
string url = string.Format(CultureInfo.InvariantCulture, LiveIdConstants.RegistrationEndpointUriFormat,
environment.HostName);
DeviceRegistrationResponse response = ExecuteRegistrationRequest(url, request);
if (!response.IsSuccess)
{
bool throwException = true;
if (DeviceRegistrationErrorCode.DeviceAlreadyExists == response.Error.RegistrationErrorCode)
{
if (!PersistToFile)
{
//If the file is not persisted, the registration will always occur (since the credentials are not
//persisted to the disk. However, the credentials may already exist. To avoid an exception being continually
//processed by the calling user, DeviceAlreadyExists will be ignored if the credentials are not persisted to the disk.
return device.User.ToClientCredentials();
}
else if (PersistIfDeviceAlreadyExists)
{
// This flag indicates that the
throwException = false;
}
}
if (throwException)
{
throw new DeviceRegistrationFailedException(response.Error.RegistrationErrorCode, response.ErrorSubCode);
}
}
if (PersistToFile || PersistIfDeviceAlreadyExists)
{
WriteDevice(environment, device);
}
return device.User.ToClientCredentials();
}
private static LiveDevice GenerateDevice(string deviceName, string devicePassword)
{
// If the deviceName hasn't been specified, it should be generated using random characters.
DeviceUserName userNameCredentials;
if (string.IsNullOrEmpty(deviceName))
{
userNameCredentials = GenerateDeviceUserName();
}
else
{
userNameCredentials = new DeviceUserName() { DeviceName = deviceName, DecryptedPassword = devicePassword };
}
return new LiveDevice() { User = userNameCredentials, Version = 1 };
}
private static LiveDevice ReadExistingDevice(EnvironmentConfiguration environment)
{
//Retrieve the file info
FileInfo file = GetDeviceFile(environment);
if (!file.Exists)
{
return null;
}
using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
{
return Deserialize<LiveDevice>("Loading Device Credentials from Disk", stream);
}
}
private static void WriteDevice(EnvironmentConfiguration environment, LiveDevice device)
{
FileInfo file = GetDeviceFile(environment);
if (!file.Directory.Exists)
{
file.Directory.Create();
}
using (FileStream stream = file.Open(FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
Serialize(stream, device);
}
}
private static DeviceRegistrationResponse ExecuteRegistrationRequest(string url, DeviceRegistrationRequest registrationRequest)
{
//Create the request that will submit the request to the server
WebRequest request = WebRequest.Create(url);
request.ContentType = "application/soap+xml; charset=UTF-8";
request.Method = "POST";
request.Timeout = 180000;
//Write the envelope to the RequestStream
using (Stream stream = request.GetRequestStream())
{
Serialize(stream, registrationRequest);
}
// Read the response into an XmlDocument and return that doc
try
{
using (WebResponse response = request.GetResponse())
{
using (Stream stream = response.GetResponseStream())
{
return Deserialize<DeviceRegistrationResponse>("Deserializing Registration Response", stream);
}
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError("Microsoft account Device Registration Failed (HTTP Code: {0}): {1}",
ex.Status, ex.Message);
if (null != ex.Response)
{
using (Stream stream = ex.Response.GetResponseStream())
{
return Deserialize<DeviceRegistrationResponse>("Deserializing Failed Registration Response", stream);
}
}
throw;
}
}
private static DeviceUserName GenerateDeviceUserName()
{
DeviceUserName userName = new DeviceUserName();
userName.DeviceName = GenerateRandomString(LiveIdConstants.ValidDeviceNameCharacters, MaxDeviceNameLength);
userName.DecryptedPassword = GenerateRandomString(LiveIdConstants.ValidDevicePasswordCharacters, MaxDevicePasswordLength);
return userName;
}
private static string GenerateRandomString(string characterSet, int count)
{
//Create an array of the characters that will hold the final list of random characters
char[] value = new char[count];
//Convert the character set to an array that can be randomly accessed
char[] set = characterSet.ToCharArray();
lock (RandomInstance)
{
//Populate the array with random characters from the character set
for (int i = 0; i < count; i++)
{
value[i] = set[RandomInstance.Next(0, set.Length)];
}
}
return new string(value);
}
#endregion
#region Private Classes
private enum EnvironmentType
{
LiveDeviceID,
OrgDeviceID
}
private sealed class EnvironmentConfiguration
{
public EnvironmentConfiguration(EnvironmentType type, string hostName, string environment)
{
if (string.IsNullOrWhiteSpace(hostName))
{
throw new ArgumentNullException("hostName");
}
this.Type = type;
this.HostName = hostName;
this.Environment = environment;
}
#region Properties
public EnvironmentType Type { get; private set; }
public string HostName { get; private set; }
public string Environment { get; private set; }
#endregion
}
private static class LiveIdConstants
{
public const string RegistrationEndpointUriFormat = @"https://{0}/ppsecure/DeviceAddCredential.srf";
public static readonly string FileNameFormat = Path.Combine(
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "LiveDeviceID"),
"{0}{1}.xml");
public const string ValidDeviceNameCharacters = "0123456789abcdefghijklmnopqrstuvqxyz";
//Consists of the list of characters specified in the documentation
public const string ValidDevicePasswordCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^*()-_=+;,./?`~";
}
#endregion
}
#region Public Classes & Enums
/// <summary>
/// Indicates an error during registration
/// </summary>
public enum DeviceRegistrationErrorCode
{
/// <summary>
/// Unspecified or Unknown Error occurred
/// </summary>
Unknown = 0,
/// <summary>
/// Interface Disabled
/// </summary>
InterfaceDisabled = 1,
/// <summary>
/// Invalid Request Format
/// </summary>
InvalidRequestFormat = 3,
/// <summary>
/// Unknown Client Version
/// </summary>
UnknownClientVersion = 4,
/// <summary>
/// Blank Password
/// </summary>
BlankPassword = 6,
/// <summary>
/// Missing Device User Name or Password
/// </summary>
MissingDeviceUserNameOrPassword = 7,
/// <summary>
/// Invalid Parameter Syntax
/// </summary>
InvalidParameterSyntax = 8,
/// <summary>
/// Invalid Characters are used in the device credentials.
/// </summary>
InvalidCharactersInCredentials = 9,
/// <summary>
/// Internal Error
/// </summary>
InternalError = 11,
/// <summary>
/// Device Already Exists
/// </summary>
DeviceAlreadyExists = 13
}
/// <summary>
/// Indicates that Device Registration failed
/// </summary>
[Serializable]
public sealed class DeviceRegistrationFailedException : Exception
{
/// <summary>
/// Construct an instance of the DeviceRegistrationFailedException class
/// </summary>
public DeviceRegistrationFailedException()
: base()
{
}
/// <summary>
/// Construct an instance of the DeviceRegistrationFailedException class
/// </summary>
/// <param name="message">Message to pass</param>
public DeviceRegistrationFailedException(string message)
: base(message)
{
}
/// <summary>
/// Construct an instance of the DeviceRegistrationFailedException class
/// </summary>
/// <param name="message">Message to pass</param>
/// <param name="innerException">Exception to include</param>
public DeviceRegistrationFailedException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Construct an instance of the DeviceRegistrationFailedException class
/// </summary>
/// <param name="code">Error code that occurred</param>
/// <param name="subCode">Subcode that occurred</param>
public DeviceRegistrationFailedException(DeviceRegistrationErrorCode code, string subCode)
: this(code, subCode, null)
{
}
/// <summary>
/// Construct an instance of the DeviceRegistrationFailedException class
/// </summary>
/// <param name="code">Error code that occurred</param>
/// <param name="subCode">Subcode that occurred</param>
/// <param name="innerException">Inner exception</param>
public DeviceRegistrationFailedException(DeviceRegistrationErrorCode code, string subCode, Exception innerException)
: base(string.Concat(code.ToString(), ": ", subCode), innerException)
{
this.RegistrationErrorCode = code;
}
/// <summary>
/// Construct an instance of the DeviceRegistrationFailedException class
/// </summary>
/// <param name="si"></param>
/// <param name="sc"></param>
private DeviceRegistrationFailedException(SerializationInfo si, StreamingContext sc)
: base(si, sc)
{
}
#region Properties
/// <summary>
/// Error code that occurred during registration
/// </summary>
public DeviceRegistrationErrorCode RegistrationErrorCode { get; private set; }
#endregion
#region Methods
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
#endregion
}
#region Serialization Classes
#region DeviceRegistrationRequest Class
[EditorBrowsable(EditorBrowsableState.Never)]
[XmlRoot("DeviceAddRequest")]
public sealed class DeviceRegistrationRequest
{
#region Constructors
public DeviceRegistrationRequest()
{
}
public DeviceRegistrationRequest(Guid applicationId, LiveDevice device)
: this()
{
if (null == device)
{
throw new ArgumentNullException("device");
}
this.ClientInfo = new DeviceRegistrationClientInfo() { ApplicationId = applicationId, Version = "1.0" };
this.Authentication = new DeviceRegistrationAuthentication()
{
MemberName = device.User.DeviceId,
Password = device.User.DecryptedPassword
};
}
#endregion
#region Properties
[XmlElement("ClientInfo")]
public DeviceRegistrationClientInfo ClientInfo { get; set; }
[XmlElement("Authentication")]
public DeviceRegistrationAuthentication Authentication { get; set; }
#endregion
}
#endregion
#region DeviceRegistrationClientInfo Class
[EditorBrowsable(EditorBrowsableState.Never)]
[XmlRoot("ClientInfo")]
public sealed class DeviceRegistrationClientInfo
{
#region Properties
[XmlAttribute("name")]
public Guid ApplicationId { get; set; }
[XmlAttribute("version")]
public string Version { get; set; }
#endregion
}
#endregion
#region DeviceRegistrationAuthentication Class
[EditorBrowsable(EditorBrowsableState.Never)]
[XmlRoot("Authentication")]
public sealed class DeviceRegistrationAuthentication
{
#region Properties
[XmlElement("Membername")]
public string MemberName { get; set; }
[XmlElement("Password")]
public string Password { get; set; }
#endregion
}
#endregion
#region DeviceRegistrationResponse Class
[EditorBrowsable(EditorBrowsableState.Never)]
[XmlRoot("DeviceAddResponse")]
public sealed class DeviceRegistrationResponse
{
#region Properties
[XmlElement("success")]
public bool IsSuccess { get; set; }
[XmlElement("puid")]
public string Puid { get; set; }
[XmlElement("Error")]
public DeviceRegistrationResponseError Error { get; set; }
[XmlElement("ErrorSubcode")]
public string ErrorSubCode { get; set; }
#endregion
}
#endregion
#region DeviceRegistrationResponse Class
[EditorBrowsable(EditorBrowsableState.Never)]
[XmlRoot("Error")]
public sealed class DeviceRegistrationResponseError
{
private string _code;
#region Properties
[XmlAttribute("Code")]
public string Code
{
get
{
return this._code;
}
set
{
this._code = value;
//Parse the error code
if (!string.IsNullOrEmpty(value))
{
//Parse the error code
if (value.StartsWith("dc", StringComparison.Ordinal))
{
int code;
if (int.TryParse(value.Substring(2), NumberStyles.Integer,
CultureInfo.InvariantCulture, out code) &&
Enum.IsDefined(typeof(DeviceRegistrationErrorCode), code))
{
this.RegistrationErrorCode = (DeviceRegistrationErrorCode)Enum.ToObject(
typeof(DeviceRegistrationErrorCode), code);
}
}
}
}
}
[XmlIgnore]
public DeviceRegistrationErrorCode RegistrationErrorCode { get; private set; }
#endregion
}
#endregion
#region LiveDevice Class
[EditorBrowsable(EditorBrowsableState.Never)]
[XmlRoot("Data")]
public sealed class LiveDevice
{
#region Properties
[XmlAttribute("version")]
public int Version { get; set; }
[XmlElement("User")]
public DeviceUserName User { get; set; }
[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode", Justification = "This is required for proper XML Serialization")]
[XmlElement("Token")]
public XmlNode Token { get; set; }
[XmlElement("Expiry")]
public string Expiry { get; set; }
[XmlElement("ClockSkew")]
public string ClockSkew { get; set; }
#endregion
}
#endregion
#region DeviceUserName Class
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class DeviceUserName
{
private string _encryptedPassword;
private string _decryptedPassword;
private bool _encryptedValueIsUpdated;
#region Constants
private const string UserNamePrefix = "11";
#endregion
#region Constructors
public DeviceUserName()
{
this.UserNameType = "Logical";
}
#endregion
#region Properties
[XmlAttribute("username")]
public string DeviceName { get; set; }
[XmlAttribute("type")]
public string UserNameType { get; set; }
[XmlElement("Pwd")]
public string EncryptedPassword
{
get
{
this.ThrowIfNoEncryption();
if (!this._encryptedValueIsUpdated)
{
this._encryptedPassword = this.Encrypt(this._decryptedPassword);
this._encryptedValueIsUpdated = true;
}
return this._encryptedPassword;
}
set
{
this.ThrowIfNoEncryption();
this.UpdateCredentials(value, null);
}
}
public string DeviceId
{
get
{
return UserNamePrefix + DeviceName;
}
}
[XmlIgnore]
public string DecryptedPassword
{
get
{
return this._decryptedPassword;
}
set
{
this.UpdateCredentials(null, value);
}
}
private bool IsEncryptionEnabled
{
get
{
//If the object is not going to be persisted to a file, then the value does not need to be encrypted. This is extra
//overhead and will not function in partial trust.
return DeviceIdManager.PersistToFile;
}
}
#endregion
#region Methods
public ClientCredentials ToClientCredentials()
{
ClientCredentials credentials = new ClientCredentials();
credentials.UserName.UserName = this.DeviceId;
credentials.UserName.Password = this.DecryptedPassword;
return credentials;
}
private void ThrowIfNoEncryption()
{
if (!this.IsEncryptionEnabled)
{
throw new NotSupportedException("Not supported when DeviceIdManager.UseEncryptionApis is false.");
}
}
private void UpdateCredentials(string encryptedValue, string decryptedValue)
{
bool isValueUpdated = false;
if (string.IsNullOrEmpty(encryptedValue) && string.IsNullOrEmpty(decryptedValue))
{
isValueUpdated = true;
}
else if (string.IsNullOrEmpty(encryptedValue))
{
if (this.IsEncryptionEnabled)
{
encryptedValue = this.Encrypt(decryptedValue);
isValueUpdated = true;
}
else
{
encryptedValue = null;
isValueUpdated = false;
}
}
else
{
this.ThrowIfNoEncryption();
decryptedValue = this.Decrypt(encryptedValue);
isValueUpdated = true;
}
this._encryptedPassword = encryptedValue;
this._decryptedPassword = decryptedValue;
this._encryptedValueIsUpdated = isValueUpdated;
}
private string Encrypt(string value)
{
if (string.IsNullOrEmpty(value))
{
return value;
}
byte[] encryptedBytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), null, DataProtectionScope.CurrentUser);
return Convert.ToBase64String(encryptedBytes);
}
private string Decrypt(string value)
{
if (string.IsNullOrEmpty(value))
{
return value;
}
byte[] decryptedBytes = ProtectedData.Unprotect(Convert.FromBase64String(value), null, DataProtectionScope.CurrentUser);
if (null == decryptedBytes || 0 == decryptedBytes.Length)
{
return null;
}
return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);
}
#endregion
}
#endregion
#endregion
#endregion
}
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Diagnostics.CodeAnalysis
Imports System.Globalization
Imports System.IO
Imports System.Net
Imports System.Runtime.Serialization
Imports System.Security.Cryptography
Imports System.ServiceModel.Description
Imports System.Text
Imports System.Xml
Imports System.Xml.Serialization
Namespace Microsoft.Crm.Services.Utility
''' <summary>
''' Management utility for the Device Id
''' </summary>
Public NotInheritable Class DeviceIdManager
#Region "Fields"
Private Shared ReadOnly RandomInstance As New Random()
Public Const MaxDeviceNameLength As Integer = 24
Public Const MaxDevicePasswordLength As Integer = 24
#End Region
#Region "Constructor"
Private Sub New()
End Sub
Shared Sub New()
PersistToFile = True
End Sub
#End Region
#Region "Properties"
''' <summary>
''' Indicates whether the registered device credentials should be persisted to the database
''' </summary>
Public Shared Property PersistToFile() As Boolean
''' <summary>
''' Indicates that the credentials should be persisted to the disk if registration fails with DeviceAlreadyExists.
''' </summary>
''' <remarks>
''' If the device already exists, there is a possibility that the credentials are the same as the current credentials that
''' are being registered. This is especially true in automated environments where the same credentials are used continually (to avoid
''' registering spurious device credentials.
''' </remarks>
Public Shared Property PersistIfDeviceAlreadyExists() As Boolean
#End Region
#Region "Methods"
''' <summary>
''' Loads the device credentials (if they exist).
''' </summary>
''' <returns></returns>
Public Shared Function LoadOrRegisterDevice() As ClientCredentials
Return LoadOrRegisterDevice(Nothing)
End Function
''' <summary>
''' Loads the device credentials (if they exist).
''' </summary>
''' <param name="deviceName">Device name that should be registered</param>
''' <param name="devicePassword">Device password that should be registered</param>
Public Shared Function LoadOrRegisterDevice(ByVal deviceName As String, ByVal devicePassword As String) As ClientCredentials
Return LoadOrRegisterDevice(Nothing, deviceName, devicePassword)
End Function
''' <summary>
''' Loads the device credentials (if they exist).
''' </summary>
''' <param name="issuerUri">URL for the current token issuer</param>
''' <remarks>
''' The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
''' </remarks>
Public Shared Function LoadOrRegisterDevice(ByVal issuerUri As Uri) As ClientCredentials
Return LoadOrRegisterDevice(issuerUri, Nothing, Nothing)
End Function
''' <summary>
''' Loads the device credentials (if they exist).
''' </summary>
''' <param name="issuerUri">URL for the current token issuer</param>
''' <param name="deviceName">Device name that should be registered</param>
''' <param name="devicePassword">Device password that should be registered</param>
''' <remarks>
''' The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
''' </remarks>
Public Shared Function LoadOrRegisterDevice(ByVal issuerUri As Uri, ByVal deviceName As String, ByVal devicePassword As String) _
As ClientCredentials
Dim credentials As ClientCredentials = LoadDeviceCredentials(issuerUri)
If Nothing Is credentials Then
credentials = RegisterDevice(Guid.NewGuid(), issuerUri, deviceName, devicePassword)
End If
Return credentials
End Function
''' <summary>
''' Registers the given device with Microsoft account with a random application ID
''' </summary>
''' <returns>ClientCredentials that were registered</returns>
Public Shared Function RegisterDevice() As ClientCredentials
Return RegisterDevice(Guid.NewGuid())
End Function
''' <summary>
''' Registers the given device with Microsoft account
''' </summary>
''' <param name="applicationId">ID for the application</param>
''' <returns>ClientCredentials that were registered</returns>
Public Shared Function RegisterDevice(ByVal applicationId As Guid) As ClientCredentials
Return RegisterDevice(applicationId, CType(Nothing, Uri))
End Function
''' <summary>
''' Registers the given device with Microsoft account
''' </summary>
''' <param name="applicationId">ID for the application</param>
''' <param name="issuerUri">URL for the current token issuer</param>
''' <returns>ClientCredentials that were registered</returns>
''' <remarks>
''' The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
''' </remarks>
Public Shared Function RegisterDevice(ByVal applicationId As Guid, ByVal issuerUri As Uri) As ClientCredentials
Return RegisterDevice(applicationId, issuerUri, Nothing, Nothing)
End Function
''' <summary>
''' Registers the given device with Microsoft account
''' </summary>
''' <param name="applicationId">ID for the application</param>
''' <param name="deviceName">Device name that should be registered</param>
''' <param name="devicePassword">Device password that should be registered</param>
''' <returns>ClientCredentials that were registered</returns>
Public Shared Function RegisterDevice(ByVal applicationId As Guid, ByVal deviceName As String, ByVal devicePassword As String) _
As ClientCredentials
Return RegisterDevice(applicationId, CType(Nothing, Uri), deviceName, devicePassword)
End Function
''' <summary>
''' Registers the given device with Microsoft account
''' </summary>
''' <param name="applicationId">ID for the application</param>
''' <param name="issuerUri">URL for the current token issuer</param>
''' <param name="deviceName">Device name that should be registered</param>
''' <param name="devicePassword">Device password that should be registered</param>
''' <returns>ClientCredentials that were registered</returns>
''' <remarks>
''' The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
''' </remarks>
Public Shared Function RegisterDevice(ByVal applicationId As Guid, ByVal issuerUri As Uri, ByVal deviceName As String, _
ByVal devicePassword As String) As ClientCredentials
If String.IsNullOrEmpty(deviceName) AndAlso (Not PersistToFile) Then
Throw New ArgumentNullException("deviceName", "If PersistToFile is false, then deviceName must be specified.")
ElseIf String.IsNullOrEmpty(deviceName) <> String.IsNullOrEmpty(devicePassword) Then
Throw New ArgumentNullException("deviceName", _
"Either deviceName/devicePassword should both be specified or they should be null.")
End If
Dim device As LiveDevice = GenerateDevice(deviceName, devicePassword)
Return RegisterDevice(applicationId, issuerUri, device)
End Function
''' <summary>
''' Loads the device's credentials from the file system
''' </summary>
''' <returns>Device Credentials (if set) or null</returns>
Public Shared Function LoadDeviceCredentials() As ClientCredentials
Return LoadDeviceCredentials(Nothing)
End Function
''' <summary>
''' Loads the device's credentials from the file system
''' </summary>
''' <param name="issuerUri">URL for the current token issuer</param>
''' <returns>Device Credentials (if set) or null</returns>
''' <remarks>
''' The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
''' </remarks>
Public Shared Function LoadDeviceCredentials(ByVal issuerUri As Uri) As ClientCredentials
'If the credentials should not be persisted to a file, then they won't be present on the disk.
If Not PersistToFile Then
Return Nothing
End If
Dim environment As EnvironmentConfiguration = DiscoverEnvironmentInternal(issuerUri)
Dim device As LiveDevice = ReadExistingDevice(environment)
If Nothing Is device OrElse Nothing Is device.User Then
Return Nothing
End If
Return device.User.ToClientCredentials()
End Function
''' <summary>
''' Discovers the Windows Live environment based on the Token Issuer
''' </summary>
Public Shared Function DiscoverEnvironment(ByVal issuerUri As Uri) As String
Return DiscoverEnvironmentInternal(issuerUri).Environment
End Function
#End Region
#Region "Private Methods"
Private Shared Function DiscoverEnvironmentInternal(ByVal issuerUri As Uri) As EnvironmentConfiguration
If Nothing Is issuerUri Then
Return New EnvironmentConfiguration(EnvironmentType.LiveDeviceID, "login.live.com", Nothing)
End If
Dim searchList As New Dictionary(Of EnvironmentType, String)()
searchList.Add(EnvironmentType.LiveDeviceID, "login.live")
searchList.Add(EnvironmentType.OrgDeviceID, "login.microsoftonline")
For Each searchPair As KeyValuePair(Of EnvironmentType, String) In searchList
If issuerUri.Host.Length > searchPair.Value.Length AndAlso issuerUri.Host.StartsWith(searchPair.Value, StringComparison.OrdinalIgnoreCase) Then
Dim environment As String = issuerUri.Host.Substring(searchPair.Value.Length)
'Parse out the environment
If "-"c = environment.Chars(0) Then
Dim separatorIndex As Integer = environment.IndexOf("."c, 1)
If -1 <> separatorIndex Then
environment = environment.Substring(1, separatorIndex - 1)
Else
environment = Nothing
End If
Else
environment = Nothing
End If
Return New EnvironmentConfiguration(searchPair.Key, issuerUri.Host, environment)
End If
Next searchPair
'In all other cases the environment is either not applicable or it is a production system
Return New EnvironmentConfiguration(EnvironmentType.LiveDeviceID, issuerUri.Host, Nothing)
End Function
Private Shared Sub Serialize(Of T)(ByVal stream As Stream, ByVal value As T)
Dim serializer As New XmlSerializer(GetType(T), String.Empty)
Dim xmlNamespaces As New XmlSerializerNamespaces()
xmlNamespaces.Add(String.Empty, String.Empty)
serializer.Serialize(stream, value, xmlNamespaces)
End Sub
Private Shared Function Deserialize(Of T)(ByVal operationName As String, ByVal stream As Stream) As T
'Read the XML into memory so that the data can be used in an exception if necessary
Using reader As New StreamReader(stream)
Return Deserialize(Of T)(operationName, reader.ReadToEnd())
End Using
End Function
Private Shared Function Deserialize(Of T)(ByVal operationName As String, ByVal xml As String) As T
'Attempt to deserialize the data. If deserialization fails, include the XML in the exception that is thrown for further
'investigation
Using reader As New StringReader(xml)
Try
Dim serializer As New XmlSerializer(GetType(T), String.Empty)
Return CType(serializer.Deserialize(reader), T)
Catch ex As InvalidOperationException
Throw New InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Unable to Deserialize XML (Operation = {0}):{1}{2}", operationName, Environment.NewLine, xml), ex)
End Try
End Using
End Function
Private Shared Function GetDeviceFile(ByVal environment As EnvironmentConfiguration) As FileInfo
Return New FileInfo(String.Format(CultureInfo.InvariantCulture, LiveIdConstants.FileNameFormat, environment.Type,If(String.IsNullOrEmpty(environment.Environment), Nothing, "-" & environment.Environment.ToUpperInvariant())))
End Function
Private Shared Function RegisterDevice(ByVal applicationId As Guid, ByVal issuerUri As Uri, ByVal device As LiveDevice) As ClientCredentials
Dim environment As EnvironmentConfiguration = DiscoverEnvironmentInternal(issuerUri)
Dim request As New DeviceRegistrationRequest(applicationId, device)
Dim url As String = String.Format(CultureInfo.InvariantCulture, LiveIdConstants.RegistrationEndpointUriFormat, environment.HostName)
Dim response As DeviceRegistrationResponse = ExecuteRegistrationRequest(url, request)
If Not response.IsSuccess Then
Dim throwException As Boolean = True
If DeviceRegistrationErrorCode.DeviceAlreadyExists = response.Error.RegistrationErrorCode Then
If Not PersistToFile Then
'If the file is not persisted, the registration will always occur (since the credentials are not
'persisted to the disk. However, the credentials may already exist. To avoid an exception being continually
'processed by the calling user, DeviceAlreadyExists will be ignored if the credentials are not persisted to the disk.
Return device.User.ToClientCredentials()
ElseIf PersistIfDeviceAlreadyExists Then
' This flag indicates that the
throwException = False
End If
End If
If throwException Then
Throw New DeviceRegistrationFailedException(response.Error.RegistrationErrorCode, response.ErrorSubCode)
End If
End If
If PersistToFile OrElse PersistIfDeviceAlreadyExists Then
WriteDevice(environment, device)
End If
Return device.User.ToClientCredentials()
End Function
Private Shared Function GenerateDevice(ByVal deviceName As String, ByVal devicePassword As String) As LiveDevice
' If the deviceName hasn't been specified, it should be generated using random characters.
Dim userNameCredentials As DeviceUserName
If String.IsNullOrEmpty(deviceName) Then
userNameCredentials = GenerateDeviceUserName()
Else
userNameCredentials = New DeviceUserName() With {.DeviceName = deviceName, .DecryptedPassword = devicePassword}
End If
Return New LiveDevice() With {.User = userNameCredentials, .Version = 1}
End Function
Private Shared Function ReadExistingDevice(ByVal environment As EnvironmentConfiguration) As LiveDevice
'Retrieve the file info
Dim file As FileInfo = GetDeviceFile(environment)
If Not file.Exists Then
Return Nothing
End If
Using stream As FileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)
Return Deserialize(Of LiveDevice)("Loading Device Credentials from Disk", stream)
End Using
End Function
Private Shared Sub WriteDevice(ByVal environment As EnvironmentConfiguration, ByVal device As LiveDevice)
Dim file As FileInfo = GetDeviceFile(environment)
If Not file.Directory.Exists Then
file.Directory.Create()
End If
Using stream As FileStream = file.Open(FileMode.CreateNew, FileAccess.Write, FileShare.None)
Serialize(stream, device)
End Using
End Sub
Private Shared Function ExecuteRegistrationRequest(ByVal url As String, ByVal registrationRequest As DeviceRegistrationRequest) _
As DeviceRegistrationResponse
'Create the request that will submit the request to the server
Dim request As WebRequest = WebRequest.Create(url)
request.ContentType = "application/soap+xml; charset=UTF-8"
request.Method = "POST"
request.Timeout = 180000
'Write the envelope to the RequestStream
Using stream As Stream = request.GetRequestStream()
Serialize(stream, registrationRequest)
End Using
' Read the response into an XmlDocument and return that doc
Try
Using response As WebResponse = request.GetResponse()
Using stream As Stream = response.GetResponseStream()
Return Deserialize(Of DeviceRegistrationResponse)("Deserializing Registration Response", stream)
End Using
End Using
Catch ex As WebException
System.Diagnostics.Trace.TraceError("Live ID Device Registration Failed (HTTP Code: {0}): {1}", ex.Status, ex.Message)
If Nothing IsNot ex.Response Then
Using stream As Stream = ex.Response.GetResponseStream()
Return Deserialize(Of DeviceRegistrationResponse)("Deserializing Failed Registration Response", stream)
End Using
End If
Throw
End Try
End Function
Private Shared Function GenerateDeviceUserName() As DeviceUserName
Dim userName As New DeviceUserName()
userName.DeviceName = GenerateRandomString(LiveIdConstants.ValidDeviceNameCharacters, MaxDeviceNameLength)
userName.DecryptedPassword = GenerateRandomString(LiveIdConstants.ValidDevicePasswordCharacters, MaxDevicePasswordLength)
Return userName
End Function
Private Shared Function GenerateRandomString(ByVal characterSet As String, ByVal count As Integer) As String
'Create an array of the characters that will hold the final list of random characters
Dim value(count - 1) As Char
'Convert the character set to an array that can be randomly accessed
Dim [set]() As Char = characterSet.ToCharArray()
SyncLock RandomInstance
'Populate the array with random characters from the character set
For i As Integer = 0 To count - 1
value(i) = [set](RandomInstance.Next(0, [set].Length))
Next i
End SyncLock
Return New String(value)
End Function
#End Region
#Region "Private Classes"
Private Enum EnvironmentType
LiveDeviceID
OrgDeviceID
End Enum
Private NotInheritable Class EnvironmentConfiguration
Public Sub New(ByVal type As EnvironmentType, ByVal hostName As String, ByVal environment As String)
If String.IsNullOrWhiteSpace(hostName) Then
Throw New ArgumentNullException("hostName")
End If
Me.Type = type
Me.HostName = hostName
Me.Environment = environment
End Sub
#Region "Properties"
Private privateType As EnvironmentType
Public Property Type() As EnvironmentType
Get
Return privateType
End Get
Private Set(ByVal value As EnvironmentType)
privateType = value
End Set
End Property
Private privateHostName As String
Public Property HostName() As String
Get
Return privateHostName
End Get
Private Set(ByVal value As String)
privateHostName = value
End Set
End Property
Private privateEnvironment As String
Public Property Environment() As String
Get
Return privateEnvironment
End Get
Private Set(ByVal value As String)
privateEnvironment = value
End Set
End Property
#End Region
End Class
Private NotInheritable Class LiveIdConstants
Public Const RegistrationEndpointUriFormat As String = "https://{0}/ppsecure/DeviceAddCredential.srf"
Public Shared ReadOnly FileNameFormat As String = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "LiveDeviceID"), "{0}{1}.xml")
Public Const ValidDeviceNameCharacters As String = "0123456789abcdefghijklmnopqrstuvqxyz"
'Consists of the list of characters specified in the documentation
Public Const ValidDevicePasswordCharacters As String = _
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^*()-_=+;,./?`~"
End Class
#End Region
End Class
#Region "Public Classes & Enums"
''' <summary>
''' Indicates an error during registration
''' </summary>
Public Enum DeviceRegistrationErrorCode
''' <summary>
''' Unspecified or Unknown Error occurred
''' </summary>
Unknown = 0
''' <summary>
''' Interface Disabled
''' </summary>
InterfaceDisabled = 1
''' <summary>
''' Invalid Request Format
''' </summary>
InvalidRequestFormat = 3
''' <summary>
''' Unknown Client Version
''' </summary>
UnknownClientVersion = 4
''' <summary>
''' Blank Password
''' </summary>
BlankPassword = 6
''' <summary>
''' Missing Device User Name or Password
''' </summary>
MissingDeviceUserNameOrPassword = 7
''' <summary>
''' Invalid Parameter Syntax
''' </summary>
InvalidParameterSyntax = 8
''' <summary>
''' Invalid Characters are used in the device credentials.
''' </summary>
InvalidCharactersInCredentials = 9
''' <summary>
''' Internal Error
''' </summary>
InternalError = 11
''' <summary>
''' Device Already Exists
''' </summary>
DeviceAlreadyExists = 13
End Enum
''' <summary>
''' Indicates that Device Registration failed
''' </summary>
<Serializable> _
Public NotInheritable Class DeviceRegistrationFailedException
Inherits Exception
''' <summary>
''' Construct an instance of the DeviceRegistrationFailedException class
''' </summary>
Public Sub New()
MyBase.New()
End Sub
''' <summary>
''' Construct an instance of the DeviceRegistrationFailedException class
''' </summary>
''' <param name="message">Message to pass</param>
Public Sub New(ByVal message As String)
MyBase.New(message)
End Sub
''' <summary>
''' Construct an instance of the DeviceRegistrationFailedException class
''' </summary>
''' <param name="message">Message to pass</param>
''' <param name="innerException">Exception to include</param>
Public Sub New(ByVal message As String, ByVal innerException As Exception)
MyBase.New(message, innerException)
End Sub
''' <summary>
''' Construct an instance of the DeviceRegistrationFailedException class
''' </summary>
''' <param name="code">Error code that occurred</param>
''' <param name="subCode">Subcode that occurred</param>
Public Sub New(ByVal code As DeviceRegistrationErrorCode, ByVal subCode As String)
Me.New(code, subCode, Nothing)
End Sub
''' <summary>
''' Construct an instance of the DeviceRegistrationFailedException class
''' </summary>
''' <param name="code">Error code that occurred</param>
''' <param name="subCode">Subcode that occurred</param>
''' <param name="innerException">Inner exception</param>
Public Sub New(ByVal code As DeviceRegistrationErrorCode, ByVal subCode As String, ByVal innerException As Exception)
MyBase.New(String.Concat(code.ToString(), ": ", subCode), innerException)
Me.RegistrationErrorCode = code
End Sub
''' <summary>
''' Construct an instance of the DeviceRegistrationFailedException class
''' </summary>
''' <param name="si"></param>
''' <param name="sc"></param>
Private Sub New(ByVal si As SerializationInfo, ByVal sc As StreamingContext)
MyBase.New(si, sc)
End Sub
#Region "Properties"
''' <summary>
''' Error code that occurred during registration
''' </summary>
Private privateRegistrationErrorCode As DeviceRegistrationErrorCode
Public Property RegistrationErrorCode() As DeviceRegistrationErrorCode
Get
Return privateRegistrationErrorCode
End Get
Private Set(ByVal value As DeviceRegistrationErrorCode)
privateRegistrationErrorCode = value
End Set
End Property
#End Region
#Region "Methods"
Public Overrides Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
MyBase.GetObjectData(info, context)
End Sub
#End Region
End Class
#Region "Serialization Classes"
#Region "DeviceRegistrationRequest Class"
<EditorBrowsable(EditorBrowsableState.Never), XmlRoot("DeviceAddRequest")> _
Public NotInheritable Class DeviceRegistrationRequest
#Region "Constructors"
Public Sub New()
End Sub
Public Sub New(ByVal applicationId As Guid, ByVal device As LiveDevice)
Me.New()
If Nothing Is device Then
Throw New ArgumentNullException("device")
End If
Me.ClientInfo = New DeviceRegistrationClientInfo() With {.ApplicationId = applicationId, .Version = "1.0"}
Me.Authentication = New DeviceRegistrationAuthentication() With {.MemberName = device.User.DeviceId, _
.Password = device.User.DecryptedPassword}
End Sub
#End Region
#Region "Properties"
<XmlElement("ClientInfo")> _
Public Property ClientInfo() As DeviceRegistrationClientInfo
<XmlElement("Authentication")> _
Public Property Authentication() As DeviceRegistrationAuthentication
#End Region
End Class
#End Region
#Region "DeviceRegistrationClientInfo Class"
<EditorBrowsable(EditorBrowsableState.Never), XmlRoot("ClientInfo")> _
Public NotInheritable Class DeviceRegistrationClientInfo
#Region "Properties"
<XmlAttribute("name")> _
Public Property ApplicationId() As Guid
<XmlAttribute("version")> _
Public Property Version() As String
#End Region
End Class
#End Region
#Region "DeviceRegistrationAuthentication Class"
<EditorBrowsable(EditorBrowsableState.Never), XmlRoot("Authentication")> _
Public NotInheritable Class DeviceRegistrationAuthentication
#Region "Properties"
<XmlElement("Membername")> _
Public Property MemberName() As String
<XmlElement("Password")> _
Public Property Password() As String
#End Region
End Class
#End Region
#Region "DeviceRegistrationResponse Class"
<EditorBrowsable(EditorBrowsableState.Never), XmlRoot("DeviceAddResponse")> _
Public NotInheritable Class DeviceRegistrationResponse
#Region "Properties"
<XmlElement("success")> _
Public Property IsSuccess() As Boolean
<XmlElement("puid")> _
Public Property Puid() As String
<XmlElement("Error")> _
Public Property [Error]() As DeviceRegistrationResponseError
<XmlElement("ErrorSubcode")> _
Public Property ErrorSubCode() As String
#End Region
End Class
#End Region
#Region "DeviceRegistrationResponse Class"
<EditorBrowsable(EditorBrowsableState.Never), XmlRoot("Error")> _
Public NotInheritable Class DeviceRegistrationResponseError
Private _code As String
#Region "Properties"
<XmlAttribute("Code")> _
Public Property Code() As String
Get
Return Me._code
End Get
Set(ByVal value As String)
Me._code = value
'Parse the error code
If Not String.IsNullOrEmpty(value) Then
'Parse the error code
If value.StartsWith("dc", StringComparison.Ordinal) Then
Dim code_Renamed As Integer
If Integer.TryParse(value.Substring(2), NumberStyles.Integer, CultureInfo.InvariantCulture, code_Renamed) _
AndAlso System.Enum.IsDefined(GetType(DeviceRegistrationErrorCode), code_Renamed) Then
Me.RegistrationErrorCode = CType(System.Enum.ToObject(GetType(DeviceRegistrationErrorCode), code_Renamed), DeviceRegistrationErrorCode)
End If
End If
End If
End Set
End Property
Private privateRegistrationErrorCode As DeviceRegistrationErrorCode
<XmlIgnore> _
Public Property RegistrationErrorCode() As DeviceRegistrationErrorCode
Get
Return privateRegistrationErrorCode
End Get
Private Set(ByVal value As DeviceRegistrationErrorCode)
privateRegistrationErrorCode = value
End Set
End Property
#End Region
End Class
#End Region
#Region "LiveDevice Class"
<EditorBrowsable(EditorBrowsableState.Never), XmlRoot("Data")> _
Public NotInheritable Class LiveDevice
#Region "Properties"
<XmlAttribute("version")> _
Public Property Version() As Integer
<XmlElement("User")> _
Public Property User() As DeviceUserName
<SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", _
MessageId:="System.Xml.XmlNode", Justification:="This is required for proper XML Serialization"), XmlElement("Token")> _
Public Property Token() As XmlNode
<XmlElement("Expiry")> _
Public Property Expiry() As String
<XmlElement("ClockSkew")> _
Public Property ClockSkew() As String
#End Region
End Class
#End Region
#Region "DeviceUserName Class"
<EditorBrowsable(EditorBrowsableState.Never)> _
Public NotInheritable Class DeviceUserName
Private _encryptedPassword As String
Private _decryptedPassword As String
Private _encryptedValueIsUpdated As Boolean
#Region "Constants"
Private Const UserNamePrefix As String = "11"
#End Region
#Region "Constructors"
Public Sub New()
Me.UserNameType = "Logical"
End Sub
#End Region
#Region "Properties"
<XmlAttribute("username")> _
Public Property DeviceName() As String
<XmlAttribute("type")> _
Public Property UserNameType() As String
<XmlElement("Pwd")> _
Public Property EncryptedPassword() As String
Get
Me.ThrowIfNoEncryption()
If Not Me._encryptedValueIsUpdated Then
Me._encryptedPassword = Me.Encrypt(Me._decryptedPassword)
Me._encryptedValueIsUpdated = True
End If
Return Me._encryptedPassword
End Get
Set(ByVal value As String)
Me.ThrowIfNoEncryption()
Me.UpdateCredentials(value, Nothing)
End Set
End Property
Public ReadOnly Property DeviceId() As String
Get
Return UserNamePrefix & DeviceName
End Get
End Property
<XmlIgnore> _
Public Property DecryptedPassword() As String
Get
Return Me._decryptedPassword
End Get
Set(ByVal value As String)
Me.UpdateCredentials(Nothing, value)
End Set
End Property
Private ReadOnly Property IsEncryptionEnabled() As Boolean
Get
'If the object is not going to be persisted to a file, then the value does not need to be encrypted. This is extra
'overhead and will not function in partial trust.
Return DeviceIdManager.PersistToFile
End Get
End Property
#End Region
#Region "Methods"
Public Function ToClientCredentials() As ClientCredentials
Dim credentials As New ClientCredentials()
credentials.UserName.UserName = Me.DeviceId
credentials.UserName.Password = Me.DecryptedPassword
Return credentials
End Function
Private Sub ThrowIfNoEncryption()
If Not Me.IsEncryptionEnabled Then
Throw New NotSupportedException("Not supported when DeviceIdManager.UseEncryptionApis is false.")
End If
End Sub
Private Sub UpdateCredentials(ByVal encryptedValue As String, ByVal decryptedValue As String)
Dim isValueUpdated As Boolean = False
If String.IsNullOrEmpty(encryptedValue) AndAlso String.IsNullOrEmpty(decryptedValue) Then
isValueUpdated = True
ElseIf String.IsNullOrEmpty(encryptedValue) Then
If Me.IsEncryptionEnabled Then
encryptedValue = Me.Encrypt(decryptedValue)
isValueUpdated = True
Else
encryptedValue = Nothing
isValueUpdated = False
End If
Else
Me.ThrowIfNoEncryption()
decryptedValue = Me.Decrypt(encryptedValue)
isValueUpdated = True
End If
Me._encryptedPassword = encryptedValue
Me._decryptedPassword = decryptedValue
Me._encryptedValueIsUpdated = isValueUpdated
End Sub
Private Function Encrypt(ByVal value As String) As String
If String.IsNullOrEmpty(value) Then
Return value
End If
Dim encryptedBytes() As Byte = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), Nothing, DataProtectionScope.CurrentUser)
Return Convert.ToBase64String(encryptedBytes)
End Function
Private Function Decrypt(ByVal value As String) As String
If String.IsNullOrEmpty(value) Then
Return value
End If
Dim decryptedBytes() As Byte = ProtectedData.Unprotect(Convert.FromBase64String(value), Nothing, DataProtectionScope.CurrentUser)
If Nothing Is decryptedBytes OrElse 0 = decryptedBytes.Length Then
Return Nothing
End If
Return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length)
End Function
#End Region
End Class
#End Region
#End Region
#End Region
End Namespace
See Also
Use the sample and helper code
Helper code: SystemUserProvider class
Use the sample and helper code
Authenticate users in Microsoft Dynamics 365
Helper code: ServerConnection class
Microsoft Dynamics 365
© 2016 Microsoft. All rights reserved. Copyright