Setting up a custom identity store using ASP.NET

ArcGIS Server can be secured with users and roles from a custom identity store. To do this, ArcGIS Server provides support for ASP.NET Membership and Role providers.

To configure a custom identity store using ASP.NET, follow these steps:

After you have configured the custom ASP.NET provider, you can view its contents in Manager and use the roles from the custom store to set permissions on web services.

Implement a custom ASP.NET provider for ArcGIS Server

To secure ArcGIS Server with users from a custom identity store, you must develop a .NET framework 3.5 ASP.NET Membership provider that implements the following methods:

To secure ArcGIS Server with roles from a custom identity store, you must develop a .NET framework 3.5 ASP.NET Role provider that implements the following methods:

NoteNote:

When implementing a custom ASP.NET provider, the recommended approach is to expose it to ArcGIS Server as a read-only identity store. If you would like to create and modify user/role information through ArcGIS Server, you will need to implement additional methods in your custom ASP.NET provider as noted in the sample implementation below.

Deploy the custom provider to ArcGIS Server

Steps:
  1. Build your custom provider, into a .dll file and copy it to the computer hosting ArcGIS Server.
  2. Install the custom provider .dll into the GAC.

    Example: gacutil /i MyCustomProvider.dll

Configure ArcGIS Server to use the custom provider

To configure ArcGIS Server to use your custom ASP.NET provider, you must specify the ASP.NET provider's type attribute. Optionally, you may also specify any runtime properties, such as user credentials, that are required by your provider.

Steps:
  1. Open the ArcGIS Sever Administrator Directory and log in.
  2. Click security > config > updateIdentityStore.
  3. Enter the User Store configuration in JSON format. The syntax is:
    {
    	"type": "ASP_NET",
    	"class": "{Membership provider's type attribute value}",
    	"properties": {
    		"Property One": "value",
       ....
    		"Property X": "value"
    	}
    }
    
  4. Enter the Role Store configuration in JSON format. The syntax is:
    {
    	"type": "ASP_NET",
    	"class": "{Role provider's type attribute value}",
    	"properties": {
    		"Property One": "value",
       ....
    		"Property X": "value"
    	}
    }
    
  5. Click update to save your configuration.

Sample implementation

The following is a sample implementation of an ASP.NET Membership and Role provider for ArcGIS Server in C#. The sample uses a ASP.NET provider that can be modified. The recommended approach is to expose the provider to ArcGIS Server as a read-only identity store.

Sample Membership provider

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Linq;
using System.Text;
using System.Web.Security;
using System.Web.Hosting;
using System.Web.Management;
using System.Web;
using System.Security.Permissions;
using System.Xml;
using System.IO; 

namespace CustomProvider
{
    public class XmlMembershipProvider : MembershipProvider
    {
        private string _appName = null;
        private string _providerName = null;

        private string _userStore;

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (String.IsNullOrEmpty(name))
                name = "CustomProvider.XmlMembershipProvider";

            base.Initialize(name, config);
            _providerName = name;

            string path = config["FileName"];
            if (String.IsNullOrEmpty(path))
                path = "C:\\temp\\IdentityStore.xml";
            else
                _userStore = path;     

            FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Write,_userStore);
            
            permission.Demand();
        }

        public override string ApplicationName
        {
            get
            {
                return _appName;
            }
            set
            {
                _appName = value;
            }
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            return true;
        }

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            MembershipUser newUser = new MembershipUser(
                _providerName,
                username,
                null,
                email,
                "Secret Question",
                username,
                true,
                false,
                DateTime.Now,               // creationDate
                DateTime.Now,               // lastLoginDate
                DateTime.Now,               // lastActivityDate
                DateTime.Now,               // lastPasswordChangedDate
                new DateTime(2000, 1, 1)    // lastLockoutDate
                );

            XmlDocument _usersList = ReadUsersFromStore();

            XmlNode newUserNode = _usersList.CreateNode(XmlNodeType.Element, "User", null);

            XmlNode newUserInfoNode = _usersList.CreateElement("UserName");
            newUserInfoNode.InnerText = username;
            newUserNode.AppendChild(newUserInfoNode);

            newUserInfoNode = _usersList.CreateElement("Password");
            newUserInfoNode.InnerText = password;
            newUserNode.AppendChild(newUserInfoNode);

            newUserInfoNode = _usersList.CreateElement("FullName");
            newUserInfoNode.InnerText = username;
            newUserNode.AppendChild(newUserInfoNode);

            newUserInfoNode = _usersList.CreateElement("Email");
            newUserInfoNode.InnerText = email;
            newUserNode.AppendChild(newUserInfoNode);

            newUserInfoNode = _usersList.CreateElement("Roles");
            newUserNode.AppendChild(newUserInfoNode);

            _usersList.DocumentElement.AppendChild(newUserNode);

            _usersList.Save(_userStore);
                  
            status = MembershipCreateStatus.Success;

            return newUser;
            
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            XmlDocument _usersList = ReadUsersFromStore();

            XmlNode nodeToDelete = _usersList.SelectSingleNode(string.Format("//*[UserName=\"{0}\"]", username));

            if (nodeToDelete != null)
            {
                _usersList.FirstChild.RemoveChild(nodeToDelete);
                _usersList.Save(_userStore);
                return true;
            }
            else
                return false;
        }

        public override bool EnablePasswordReset
        {
            get { return true; }
        }

        public override bool EnablePasswordRetrieval
        {
            get { return true; }
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {

            MembershipUserCollection usersColl = new MembershipUserCollection();
            XmlNodeList matchingNodes = null;

            XmlDocument _usersList = ReadUsersFromStore();
            matchingNodes = _usersList.SelectNodes(string.Format("//*[contains(UserName,\"{0}\")]", usernameToMatch));

            totalRecords = 0;

            if (matchingNodes != null && matchingNodes.Count != 0)
            {
                totalRecords = matchingNodes.Count;

                foreach (XmlNode node in matchingNodes)
                {
                    MembershipUser newUser = new MembershipUser(
                        _providerName,
                        node["UserName"].InnerText,
                        null,
                        node["Email"].InnerText,
                        "",
                        node["UserName"].InnerText,
                        true,
                        false,
                        DateTime.Now,               // creationDate
                        DateTime.Now,               // lastLoginDate
                        DateTime.Now,               // lastActivityDate
                        DateTime.Now,               // lastPasswordChangedDate
                        new DateTime(2000, 1, 1)    // lastLockoutDate
                        );

                    usersColl.Add(newUser);
                }
            }

            return usersColl;
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {

            MembershipUserCollection usersColl = new MembershipUserCollection();

            XmlDocument _usersList = ReadUsersFromStore();

            XmlNodeList userNodes = _usersList.GetElementsByTagName("User");

            foreach (XmlNode node in userNodes)
            {
                MembershipUser newUser = new MembershipUser(
                    _providerName,
                    node["UserName"].InnerText,
                    null,
                    node["Email"].InnerText,
                    "Secret Question",
                    node["UserName"].InnerText,
                    true,
                    false,
                    DateTime.Now,               // creationDate
                    DateTime.Now,               // lastLoginDate
                    DateTime.Now,               // lastActivityDate
                    DateTime.Now,               // lastPasswordChangedDate
                    new DateTime(2000, 1, 1)    // lastLockoutDate
                    );

                usersColl.Add(newUser);                 
            }

            totalRecords = userNodes.Count;

            return usersColl;                  
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            XmlDocument _usersList = ReadUsersFromStore();

            MembershipUser user = null;

            XmlNode node = _usersList.SelectSingleNode(string.Format("//*[UserName=\"{0}\"]", username));

            if (node != null)
            {

                user = new MembershipUser(
                    _providerName,
                    node["UserName"].InnerText,
                    null,
                    node["Email"].InnerText,
                    "Secret Question",
                    node["UserName"].InnerText,
                    true,
                    false,
                    DateTime.Now,               // creationDate
                    DateTime.Now,               // lastLoginDate
                    DateTime.Now,               // lastActivityDate
                    DateTime.Now,               // lastPasswordChangedDate
                    new DateTime(2000, 1, 1)    // lastLockoutDate
                    );
            }

            return user;

        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override string GetUserNameByEmail(string email)
        {
            throw new NotImplementedException();
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredPasswordLength
        {
            get { throw new NotImplementedException(); }
        }

        public override int PasswordAttemptWindow
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { throw new NotImplementedException(); }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresUniqueEmail
        {
            get { throw new NotImplementedException(); }
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            XmlDocument _usersList = ReadUsersFromStore();

            String username = user.UserName;
            XmlNode nodeToUpdate = _usersList.SelectSingleNode(string.Format("//*[UserName=\"{0}\"]", username));

            if (nodeToUpdate != null)
            {
                nodeToUpdate["Email"].InnerText = user.Email;
                _usersList.Save(_userStore);
            }
            
        }

        public override bool ValidateUser(string username, string password)
        {
            XmlDocument _usersList = ReadUsersFromStore();

            XmlNode node = _usersList.SelectSingleNode(string.Format("//*[UserName=\"{0}\"]", username));

            if (node != null && username.Equals(node["UserName"].InnerText) && password.Equals(node["Password"].InnerText))
                return true;
            else
                return false;
        }

        private XmlDocument ReadUsersFromStore()
        {
            XmlDocument _usersList = new XmlDocument();
            _usersList.Load(_userStore);
            return _usersList;
        }

    } /* XmlFileMembershipProvider */
}

Sample Role provider

using System;
using System.Collections.Generic;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Linq;
using System.Text;
using System.Web.Security;
using System.Web.Hosting;
using System.Web.Management;
using System.Web;
using System.Security.Permissions;
using System.Xml;
using System.IO; 

namespace CustomProvider
{
    public class XmlRoleProvider : RoleProvider
    {
        private string _appName = null;
        private string _providerName = null;

        private string _roleStore = null;
        XmlDocument _xmlRoleList = null;

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (String.IsNullOrEmpty(name))
                name = "CustomProvider.XmlRoleProvider";

            base.Initialize(name, config);
            _providerName = name;

            string path = config["FileName"];
            if (String.IsNullOrEmpty(path))
                path = "C:\\temp\\IdentityStore.xml";
            else
                _roleStore = path;

            FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Write, _roleStore);

            permission.Demand();
        }

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            XmlDocument userRoleDoc = ReadUserRolesFromStore();
            foreach (string user in usernames)
            {
                XmlNode userRoleNode = userRoleDoc.SelectSingleNode(string.Format("//*[UserName=\"{0}\"]", user));

                String roleList = userRoleNode["Roles"].InnerText;

                foreach (string role in roleNames)
                {
                    if(roleList.Equals(""))
                        roleList += role;
                    else
                        roleList += "," + role;
                }

                userRoleNode.RemoveChild(userRoleNode.LastChild);

                XmlNode newRoleListNode = userRoleDoc.CreateElement("Roles");
                newRoleListNode.InnerText = roleList;
                userRoleNode.AppendChild(newRoleListNode);
            }
            userRoleDoc.Save(_roleStore);
        }

        public override string ApplicationName
        {
            get
            {
                return _appName;
            }
            set
            {
                _appName = value;
            }
        }

        public override void CreateRole(string roleName)
        {
            ReadRolesFromStore();

            XmlNode newRoleNode = _xmlRoleList.CreateNode(XmlNodeType.Element, "Role", null);

            XmlNode newUserInfoNode = _xmlRoleList.CreateElement("RoleName");
            newUserInfoNode.InnerText = roleName;
            newRoleNode.AppendChild(newUserInfoNode);

            _xmlRoleList.DocumentElement.AppendChild(newRoleNode);

            _xmlRoleList.Save(_roleStore);
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        { 
            ReadRolesFromStore();

            XmlNode nodeToDelete = _xmlRoleList.SelectSingleNode(string.Format("//*[RoleName=\"{0}\"]", roleName));

            if (nodeToDelete != null)
            {
                _xmlRoleList.FirstChild.RemoveChild(nodeToDelete);
                _xmlRoleList.Save(_roleStore);
                return true;
            }
            else
                return false;
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            throw new NotImplementedException();
        }

        public override string[] GetAllRoles()
        {
            ArrayList roleArrayList = ReadRolesFromStore();

            return (string[])roleArrayList.ToArray(typeof(string));
        }

        public override string[] GetRolesForUser(string username)
        {
            XmlDocument xmlUserRolesList = ReadUserRolesFromStore();

            XmlNode userRoleNode = xmlUserRolesList.SelectSingleNode(string.Format("//*[UserName=\"{0}\"]", username));

            String[] roleList = userRoleNode["Roles"].InnerText.Split(',');

            if (roleList[0] == "")
                return new string[0];
            else
                return roleList;
        }

        public override string[] GetUsersInRole(string roleName)
        {
            ArrayList userList = new ArrayList();

            XmlDocument xmlUserRolesList = ReadUserRolesFromStore();

            XmlNodeList userRoleNodes = xmlUserRolesList.GetElementsByTagName("User");

            if (userRoleNodes != null)
            {
                int numRoles = userRoleNodes.Count;
                foreach(XmlNode node in userRoleNodes) {
                    String[] roleList = node["Roles"].InnerText.Split(',');

                    foreach (string item in roleList)
                    {
                        if (item.Equals(roleName))
                        {
                            String username = node["UserName"].InnerText;
                            userList.Add(username);
                            break;
                        }
                    }
                }
            }

            return (string[])userList.ToArray(typeof(string));
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            throw new NotImplementedException();
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            XmlDocument userRoleDoc = ReadUserRolesFromStore();
            foreach (string user in usernames)
            {
                XmlNode userRoleNode = userRoleDoc.SelectSingleNode(string.Format("//*[UserName=\"{0}\"]", user));

                ArrayList roleList = new ArrayList();
                roleList.AddRange(userRoleNode["Roles"].InnerText.Split(','));

                foreach (string role in roleNames)
                {
                    if (roleList.Contains(role))
                        roleList.Remove(role);
                }

                userRoleNode.RemoveChild(userRoleNode.LastChild);

                XmlNode newRoleListNode = userRoleDoc.CreateElement("Roles");
                if( roleList.Count > 0 )
                newRoleListNode.InnerText = string.Join(",", (string[])roleList.ToArray(Type.GetType("System.String")));
                userRoleNode.AppendChild(newRoleListNode);
            }
            userRoleDoc.Save(_roleStore);
        }

        public override bool RoleExists(string roleName)
        {
            throw new NotImplementedException();
        }

        private ArrayList ReadRolesFromStore()
        {
            _xmlRoleList = new XmlDocument();
            _xmlRoleList.Load(_roleStore);

            XmlNodeList roleNodes = _xmlRoleList.GetElementsByTagName("Role");

            ArrayList roleArrayList = null;

            if (roleNodes != null)
            {
                roleArrayList = new ArrayList();

                foreach (XmlNode node in roleNodes)
                {
                    roleArrayList.Add( node["RoleName"].InnerText);
                }
            }
            return roleArrayList;
        }

        private XmlDocument ReadUserRolesFromStore()
        {
            XmlDocument xmlUserRolesList = new XmlDocument();
            xmlUserRolesList.Load(_roleStore);

            return xmlUserRolesList;
        }
    }
}

Sample Identity Store configuration JSON

User Store JSON

{
  "type": "ASP_NET",
  "class": "CustomProvider.XmlMembershipProvider,CustomProvider,Version=1.0.0.0,Culture=Neutral,PublicKeyToken=b02390eb7f2c02c4",
  "properties": {
    "FileName": "C:\\arcgisserver\\identitystore\\IdentityStore.xml"
  }
}

Role Store JSON

{
  "type": "ASP_NET",
  "class": "CustomProvider.XmlRoleProvider,CustomProvider,Version=1.0.0.0,Culture=Neutral,PublicKeyToken=b02390eb7f2c02c4",
  "properties": {
    "FileName": "C:\\arcgisserver\\identitystore\\IdentityStore.xml"
  }
}

Sample Identity Store

<IdentityStore>
  <User>
    <UserName>amy</UserName>
    <Password>amy</Password>
    <FullName>amy</FullName>
    <Email>amy@amy.amy</Email>
    <Roles>admins</Roles>
  </User>
  <Role>
    <RoleName>admins</RoleName>
  </Role>
</IdentityStore>

9/1/2015