Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:12
    John Hodgkinson
    0

    Custom Membership and Role provider integration with external CRM

    We had the requirement of integrating membership and roles with a cloud-based CRM system (Avectra/Abila netFORUM) with Umbraco 6.1.6. We found bits and pieces to put this started but had trouble finding a full sample solution.

    I'm including the current code files in hope this may help some others along the way. Keep in mind this initial setup was just completed in the last day or so and is subject to change. We would also appreciate any comments on improvements as well.

    We wanted to tie into the "Public Access" roles with our netFORUM roles which we access via web service calls and also use a HttpHandler and a custom data type to also protect media files as well using the netFORUM roles as well. In addition, outside of the page and media roles/permissions, we also wanted to be able to protect Controllers on the MVC using back office users/user types and also individual member accounts and groups on the front-end. Although I don't believe you can use both at the same time because the public access authentication fires before the MVC Authorize Attribute takes place (I may be wrong about this).

    The two classes for the Membership Provider (CustomMembershipProvider.cs) and Roles Provider (CustomMemberRoleProvider.cs). I'm trying to insert the code below but the insert code functionality may break this up but will try to put the code in seperate posts...

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:15
    John Hodgkinson
    0

    CustomMembershipProivder.cs

    #region namespace
    

    using System; using System.Collections.Generic; using System.Security; using System.Security.Permissions; using System.Runtime.CompilerServices; using System.Text; using System.Web.Security; using System.Configuration; using umbraco.BusinessLogic; using System.Security.Cryptography; using System.Web.Util; using System.Collections.Specialized; using System.Configuration.Provider; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.member; using ncba.CRM;

    endregion

    namespace ncba.MVC.CustomProviders { public class CustomMembershipProvider : umbraco.providers.members.UmbracoMembershipProvider { #region Local Variables private string _ApplicationName = "CustomMembershipProvider"; #endregion

        #region Properties
        /// <summary>
        /// The name of the application using the custom membership provider.
        /// </summary>
        /// <value></value>
        /// <returns>The name of the application using the custom membership provider.</returns>
        public override string ApplicationName
        {
            get
            {
                return _ApplicationName;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                    throw new ProviderException("ApplicationName cannot be empty.");
    
    
                if (value.Length > 0x100)
                    throw new ProviderException("Provider application name too long.");
    
    
                _ApplicationName = value;
            }
        }
        #endregion
    
        #region Methods
        public override bool ValidateUser(string username, string password)
        {
            //local helper objects
            ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
    
            //removed persisted account data
            w3AccountHelper.RemoveUserFromCache();
    
            //local helper objects
            ncba.CRM.Global.NumberHelper numberhelper = new ncba.CRM.Global.NumberHelper();
    
            //check if NCBA number or email based login
            ncba.CRM.WebAccount.User user = new CRM.WebAccount.User();
            if (!numberhelper.IsNumeric(username))
                user = new CRM.WebAccount.User().GetUserAccount(username, password);
            else
                user = new CRM.WebAccount.User().GetUserAccountByNcbaNumber(username, password);
    
            //make sure user valid
            if (user != null)
            {
                //make sure valid customer key returned
                if (user.CustomerKey != "00000000-0000-0000-0000-000000000000")
                {
                    //add user account data to cache
                    w3AccountHelper.AddUserToCache(user, false);
    
                    //valid login
                    return true;
                }
            }
    
            //invalid login
            return false;
        }
    
        /// <summary>
        /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
        /// </summary>
        /// <param name="username">The name of the user to get information for.</param>
        /// <param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the specified user's information from the data source.
        /// </returns>
        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            //local helper objects
            ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
    
            //get user from cache
            ncba.CRM.WebAccount.User user = w3AccountHelper.GetUserFromCache();
    
            //if user exists, convert to .NET Membership User
            if (user == null) return null;
            else return ConvertToMembershipUser(user);
        }
    
        /// <summary>
        /// Gets information from the data source for a user based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user.
        /// </summary>
        /// <param name="providerUserKey">The unique identifier for the membership user to get information for.</param>
        /// <param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the specified user's information from the data source.
        /// </returns>
        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            //local helper objects
            ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
    
            //get user based on provider key NF customer key)
            ncba.CRM.WebAccount.User user = new ncba.CRM.WebAccount.User().GetUserAccount(Convert.ToString(providerUserKey));
    
            //if user exists, convert to .NET Membership User
            if (user == null) return null;
            else return ConvertToMembershipUser(user);
        }
    
        private MembershipUser ConvertToMembershipUser(ncba.CRM.WebAccount.User user)
        {
            if (user == null) return null;
            else
            {
                //create new .NET Membership User using NF user info
                MembershipUser member = new MembershipUser(_ApplicationName, user.CustomerKey, user.CustomerKey, user.EmailAddress, string.Empty, string.Empty, true, false, DateTime.Now,
                  DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
    
                //return Membership User
                return member;
            }
        }
        #endregion
    }
    

    }

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:16
    John Hodgkinson
    0

    CustomMemberRoleProvider.cs

    #region namespace
    

    using System; using System.Collections.Generic; using System.Text; using System.Web.Security; using System.Configuration; using umbraco.BusinessLogic; using System.Security.Cryptography; using System.Web.Util; using System.Collections.Specialized; using System.Configuration.Provider; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.member; using System.Collections; using ncba.MVC.Helpers;

    endregion

    namespace ncba.MVC.CustomProviders { public class CustomMemberRoleProvider : RoleProvider { #region Local Variables private string _ApplicationName = "CustomMemberRoleProvider"; #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the name of the application to store and retrieve role information for.
        /// </summary>
        /// <value></value>
        /// <returns>The name of the application to store and retrieve role information for.</returns>
        public override string ApplicationName
        {
            get
            {
                return _ApplicationName;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                    throw new ProviderException("ApplicationName cannot be empty.");
    
                if (value.Length > 0x100)
                    throw new ProviderException("Provider application name too long.");
    
                _ApplicationName = value;
            }
        }
        #endregion
    
        #region Initialization Method
        /// <summary>
        /// Initializes the provider.
        /// </summary>
        /// <param name="name">The friendly name of the provider.</param>
        /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
        /// <exception cref="T:System.ArgumentNullException">The name of the provider is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">An attempt is made to call 
        /// <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)"></see> on a provider 
        /// after the provider has already been initialized.</exception>
        /// <exception cref="T:System.ArgumentException">The name of the provider has a length of zero.</exception>
        public override void Initialize(string name, NameValueCollection config)
        {
            // Verify that config isn't null
            if (config == null)
                throw new ArgumentNullException("config");
    
            // Assign the provider a default name if it doesn't have one
            if (String.IsNullOrEmpty(name))
                name = _ApplicationName;
    
            // Add a default "description" attribute to config if the
            // attribute doesn’t exist or is empty
            if (string.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Custom Member Role Provider");
            }
    
            // Call the base class's Initialize method
            base.Initialize(name, config);
        }
        #endregion
    
        #region Methods
        /// <summary>
        /// Adds the specified user names to the specified roles for the configured applicationName.
        /// </summary>
        /// <param name="usernames">A string array of user names to be added to the specified roles.</param>
        /// <param name="roleNames">A string array of the role names to add the specified user names to.</param>
        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            throw new NotImplementedException();
        }
    
        /// <summary>
        /// Adds a new role to the data source for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to create.</param>
        public override void CreateRole(string roleName)
        {
            throw new NotImplementedException();
        }
    
        /// <summary>
        /// Removes a role from the data source for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to delete.</param>
        /// <param name="throwOnPopulatedRole">If true, throw an exception if roleName has one or more members and do not delete roleName.</param>
        /// <returns>
        /// true if the role was successfully deleted; otherwise, false.
        /// </returns>
        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            throw new NotImplementedException();
        }
    
        /// <summary>
        /// Gets an array of user names in a role where the user name contains the specified user name to match.
        /// </summary>
        /// <param name="roleName">The role to search in.</param>
        /// <param name="usernameToMatch">The user name to search for.</param>
        /// <returns>
        /// A string array containing the names of all the users where the user name matches usernameToMatch and the user is a member of the specified role.
        /// </returns>
        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {          
            throw new NotImplementedException();
        }
    
        /// <summary>
        /// Gets a list of all the roles for the configured applicationName.
        /// </summary>
        /// <returns>
        /// A string array containing the names of all the roles stored in the data source for the configured applicationName.
        /// </returns>
        public override string[] GetAllRoles()
        {
            //get helper objects
            ncba.CRM.Global.w3CollectionsHelper w3Helper = new ncba.CRM.Global.w3CollectionsHelper();
    
            //get all web roles as string array
            string[] arrRoles = w3Helper.Get_Web_Roles_Array();
    
            //return data
            return arrRoles;
        }
    
        /// <summary>
        /// Gets a list of the roles that a specified user is in for the configured applicationName.
        /// </summary>
        /// <param name="username">The user to return a list of roles for.</param>
        /// <returns>
        /// A string array containing the names of all the roles that the specified user is in for the configured applicationName.
        /// </returns>
        public override string[] GetRolesForUser(string username)
        {
            //get helper objects
            ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
    
            //get user from cache
            ncba.CRM.WebAccount.User user = w3AccountHelper.GetUserFromCache();
    
            //return roles
            return user.MemberRolesValues.Split(',');
        }
    
        /// <summary>
        /// Gets a list of users in the specified role for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to get the list of users for.</param>
        /// <returns>
        /// A string array containing the names of all the users who are members of the specified role for the configured applicationName.
        /// </returns>
        public override string[] GetUsersInRole(string roleName)
        {
            throw new NotImplementedException();
        }
    
        public override bool IsUserInRole(string username, string roleName)
        {
            //get helper objects
            ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
    
            //get user from cache
            ncba.CRM.WebAccount.User user = w3AccountHelper.GetUserFromCache();
    
            //make sure user valid
            if (user != null)
            {
                //return back role check
                return user.IsUserInRole(Convert.ToString(user.NcbaNumber), roleName);
            }
            else
            {
                return false;
            }
        }
    
    
        /// <summary>
        /// Removes the specified user names from the specified roles for the configured applicationName.
        /// </summary>
        /// <param name="usernames">A string array of user names to be removed from the specified roles.</param>
        /// <param name="roleNames">A string array of role names to remove the specified user names from.</param>
        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            throw new NotImplementedException();
        }
    
        /// <summary>
        /// Gets a value indicating whether the specified role name already exists in the role data source for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to search for in the data source.</param>
        /// <returns>
        /// true if the role name already exists in the data source for the configured applicationName; otherwise, false.
        /// </returns>
        public override bool RoleExists(string roleName)
        {
            throw new NotImplementedException();
        }
        #endregion
    
    }
    

    }

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:17
    John Hodgkinson
    0

    then hookup in web.config under system.web:

        <!-- Membership Provider -->
    <membership defaultProvider="CustomMembershipProvider" userIsOnlineTimeWindow="15">
      <providers>
        <clear />
        <add name="CustomMembershipProvider" type="ncba.MVC.CustomProviders.CustomMembershipProvider" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Another Type" passwordFormat="Hashed"/>
        <!--<add name="UmbracoMembershipProvider" type="umbraco.providers.members.UmbracoMembershipProvider" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Another Type" passwordFormat="Hashed" />-->
        <add name="UsersMembershipProvider" type="umbraco.providers.UsersMembershipProvider" enablePasswordRetrieval="false" enablePasswordReset="false" 
             requiresQuestionAndAnswer="false" passwordFormat="Hashed" />
      </providers>
    </membership>
    <!-- added by NH to support membership providers in access layer -->
    <roleManager enabled="true" defaultProvider="CustomMemberRoleProvider" cacheRolesInCookie="true">
      <providers>
        <clear />
        <add name="CustomMemberRoleProvider" type="ncba.MVC.CustomProviders.CustomMemberRoleProvider" />
      </providers>
    </roleManager>
    <!--<roleManager enabled="true" defaultProvider="UmbracoRoleProvider">
      <providers>
        <clear />
        <add name="UmbracoRoleProvider" type="umbraco.providers.members.UmbracoRoleProvider" />
      </providers>
    </roleManager>-->
    
  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:18
    John Hodgkinson
    0

    our MemberLogin Model:

    using System;
    

    using System.Data; using System.Text; using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Security; using System.ComponentModel.DataAnnotations;

    namespace ncba.MVC.Models.NCBA.MemberLogin { public class MemberLoginModel { [Required(ErrorMessage = "User Name is required"), Display(Name = "User Name")] public string Username { get; set; }

        [Required(ErrorMessage = "Password is required"), Display(Name = "Password"), DataType(DataType.Password)]
        public string Password { get; set; }
    
        [Display(Name = "Remember Me?")]
        public bool RememberMe { get; set; }
    
        [ScaffoldColumn(false)]
        public String ReturnUrl { get; set; }
    }
    

    }

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:19
    John Hodgkinson
    0

    our MemberLogin Controller:

    using System;
    

    using System.Text; using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Security; using System.Security.Principal; using Umbraco.Core; using Umbraco.Web; using ncba.CRM; using ncba.MVC.Models.NCBA.MemberLogin; using ncba.MVC.CustomProviders;

    namespace ncba.MVC.Controllers.NCBA { public class MemberLoginSurfaceController : Umbraco.Web.Mvc.SurfaceController {

        [HttpGet]
        public ActionResult MemberLogin()
        {
            //create new instance of model
            MemberLoginModel model = new MemberLoginModel();
    
            //check for possible redirect url options
            String rawUrl = Request.RawUrl;
            String urlReferrer = Request.UrlReferrer != null ? HttpUtility.UrlEncode(Request.UrlReferrer.PathAndQuery) : String.Empty;
            String reqUrl = Request["ReturnUrl"] != null ? Request["ReturnUrl"] : String.Empty;
            //TempData["Status"] = String.Format("urlReferrer = '{0}' returnUrl = '{1}' rawUrl = '{2}'", urlReferrer, reqUrl, rawUrl);
    
            //find a return url to use
            String returnUrl = !String.IsNullOrEmpty(rawUrl) ? rawUrl : urlReferrer;
            if (!String.IsNullOrEmpty(reqUrl))
                returnUrl = reqUrl;
    
            //set return url in model
            model.ReturnUrl = returnUrl;
    
            //return partial
            return PartialView("MemberLogin", model);
        }
    
        [HttpPost]
        public ActionResult MemberLogin(MemberLoginModel model)
        {
            //make sure passes validations
            if (!ModelState.IsValid)
            {
                return PartialView("MemberLogin", model);
            }
    
            //get custom provider
            CustomMembershipProvider mp = new CustomMembershipProvider();
    
            //check if valid            
            if (mp.ValidateUser(model.Username, model.Password))
            {
                //get helper objects
                ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
    
                //get user from cache
                ncba.CRM.WebAccount.User user = w3AccountHelper.GetUserFromCache();
    
                //set forms authentication cookie
                FormsAuthentication.SetAuthCookie(model.Username, model.RememberMe);
    
                //set forms roles cookie
                HttpCookie authCookie = HttpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
                if (authCookie != null)
                {
                    FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
                    GenericPrincipal userPrincipal = new GenericPrincipal(new GenericIdentity(authTicket.Name), user.MemberRolesValues.Split(','));
                    HttpContext.User = userPrincipal;
                }
    
                //check if persist on NF level
                if (model.RememberMe)
                {
                    //persist user account data outisde of session
                    w3AccountHelper.AddUserToCache(user, true);
                }
    
                //go to return url
                return Redirect(model.ReturnUrl);
            }
            else
            {
                //set invalid login message
                TempData["Status"] = "Invalid username or password";
    
                //return back to current page
                return RedirectToCurrentUmbracoPage();
            }
        }
    
        [HttpGet]
        public ActionResult MemberLogout(string returnUrl)
        {
            //local helper objects
            ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
            ncba.CRM.Global.NumberHelper numberhelper = new ncba.CRM.Global.NumberHelper();
    
            //remove forms authentication cookie
            FormsAuthentication.SignOut();
    
            //removed persisted account data
            w3AccountHelper.RemoveUserFromCache();
    
            //clear session
            Session.Clear();
    
            //go back to home
            return Redirect("/");
        }
    }
    

    }

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:21
    John Hodgkinson
    0

    our MemberLogin view:

    @model ncba.MVC.Models.NCBA.MemberLogin.MemberLoginModel
    

    @using ncba.CRM.WebAccount

    @{ //unauthorized access check if (TempData["Status"] == "Unauthorized Access") { // user does not possess the required role permission string unauthorizedUrl = Convert.ToString(System.Configuration.ConfigurationManager.AppSettings["ncbaAppsUnauthorizedLink"]); Response.Redirect(unauthorizedUrl); }

    //local helper objects
    ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
    ncba.CRM.Global.LinksHelper linksHelper = new ncba.CRM.Global.LinksHelper();
    ncba.CRM.Global.EnvironmentHelper envHelper = new ncba.CRM.Global.EnvironmentHelper();
    
    //make sure secure connecction
    if (!Request.IsSecureConnection && !envHelper.IsDevelopmentEnvironment())
    {
        Response.Redirect(linksHelper.CreateUrl(Request.RawUrl, true));
    }
    
    //override model return url
    //Model.ReturnUrl = "http://www.google.com";
    
    //check if logged in user
    ncba.CRM.WebAccount.User user = w3AccountHelper.GetUserFromCache();
    
    //check user object
    if (user != null)
    {
        <p class="invalid-data">Welcome back <b>@user.PreferredName</b>!!!</p>
        <p>Full Name: @user.FullName</p>    
        <p>Organization: @user.oIndividual.Customer.cst_org_name_dn</p>
        <p>@Html.ActionLink("Log out", "MemberLogout", "MemberLoginSurface")</p>
    }
    else
    {           
        using (Html.BeginUmbracoForm("MemberLogin", "MemberLoginSurface", FormMethod.Post, new {@class = "login-form"}))
        {
            @Html.AntiForgeryToken()
            @Html.ValidationSummary(true)
    
            <div class="login-form-column">
    
                <div class="editor-label">
                    <label for="Username">User Name</label>
                </div>
    
                <div class="editor-field">
                    @Html.TextBoxFor(model => model.Username)
                    @Html.ValidationMessageFor(model => model.Username, String.Empty, new { @class = "error" }) 
                </div>
    
                <div class="editor-label">
                    <label for="Password">Password</label>
                </div>
    
                <div class="editor-field">
                    @Html.PasswordFor(model => model.Password)
                    @Html.ValidationMessageFor(model => model.Password, String.Empty, new { @class = "error" }) 
                </div>
    
                <div class="editor-label">
                    <label for="RememberMe">Remember Me?</label>
                </div>
    
                <div class="editor-field">
                    @Html.CheckBoxFor(model => model.RememberMe)
                </div>
    
                @Html.HiddenFor(model => model.ReturnUrl)
    
                <button type="submit" name="submit">Sign In<span class="ss-check"></span></button>
    
            </div>
    
            <div class="login-form-column">
    
                <p class="helpful-link">Trouble logging in? <a href="#">Click Here!</a></p>
                <p class="helpful-link">Need to sign up? <a class="join" href="#">Join Today!</a></p>   
    
            </div>
        }
    
    }   
    
    <p class="invalid-data">@TempData["Status"]</p>
    

    }

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:26
    John Hodgkinson
    0

    SOrry I forgot to mention in the initial post this was initegrated using Umbraco 6.1.6...

    below is a snapshot of the netFORUM roles pulled into public access (we will have potentially hundreds of roles, some test roles in there now):

    enter image description here

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:28
    John Hodgkinson
    0

    attempting to access authenticated page:

    enter image description here

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:31
    John Hodgkinson
    0

    currently a member of one of the allowed roles, so we're in:

    enter image description here

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:33
    John Hodgkinson
    0

    If we not a member of one of the allowed roles, we're denied access:

    enter image description here

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:37
    John Hodgkinson
    0

    now on the media file authentication we crated a custom data type and hooked it up into Umbraco for easy use in document types (in this case a media file type):

    using System;
    

    using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls;

    namespace ncba.customDataTypes { public partial class w3NCBAAccountRoleNamesList : System.Web.UI.UserControl, umbraco.editorControls.userControlGrapper.IUsercontrolDataEditor { public string umbracoValue;

        protected void Page_Load(object sender, EventArgs e)
        {
            //get helper objects
            ncba.business.controls.Helper controlsHelper = new ncba.business.controls.Helper();
    
            //make sure not postback
            if (!Page.IsPostBack)
            {
                //get helper object
                ncba.CRM.Global.w3CollectionsHelper w3Helper = new ncba.CRM.Global.w3CollectionsHelper();
    
                //set dropdownlist binding properties
                lbx_roles.DataSource = w3Helper.Get_Web_Roles();
                lbx_roles.DataTextField = "rolename";
                lbx_roles.DataValueField = "rolename";
                lbx_roles.DataBind();
                lbx_roles.Items.Add(new ListItem("", ""));
            }
            else
            {
                //get selected value
                umbracoValue = controlsHelper.GetListBoxValue(lbx_roles, ",");
            }
    
            //pre-select value
            controlsHelper.SetListBoxSelectedValues(lbx_roles, umbracoValue, ',');
        }
    
        public object value
        {
            get
            {
                return umbracoValue;
            }
            set
            {
                umbracoValue = value.ToString();
            }
        }
    }
    

    }

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:40
    John Hodgkinson
    0

    hooked up the new data type into the back office and exposed it to our media file:

    enter image description here

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:44
    John Hodgkinson
    0

    and then create a new property for our media file type using the above data type:

    enter image description here

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:48
    John Hodgkinson
    0

    the we created the HttpRequestIntercept.cs HttpHandler for file extension types we wanted to protect:

    using System;
    

    using System.Collections.Generic; using System.Linq; using System.Web; using System.IO; using System.Xml; using System.Xml.XPath; using umbraco; using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.web; using umbraco.NodeFactory; using Umbraco.Core; using ncba.business; using ncba.CRM;

    public class HttpRequestIntercept : IHttpHandler { //local declares private string _allowedRoles = String.Empty; private bool _excludeFromSearch = false;

    public string AllowedRoles
    {
        get { return _allowedRoles; }
        set { _allowedRoles = value; }
    }
    
    public bool ExcludeFromSearch
    {
        get { return _excludeFromSearch; }
        set { _excludeFromSearch = value; }
    }
    
    //get node from url
    public String GetMediaNodeIdByUrl(string url)
    {
        //local helper declares
        ncba.business.global.CMSHelper cmsHelper = new ncba.business.global.CMSHelper();
    
        //make so no extraneous domain info from url
        url = url.Substring(url.IndexOf("/media/"));
    
        //get property id from url
        string propertyId = url.Replace("/media/", "");
        propertyId = propertyId.Substring(0, propertyId.IndexOf("/"));
    
        //get node value
        string nodeId = cmsHelper.GetNodeIdByPropertyId(propertyId);
    
        //make sure node ok
        if (!String.IsNullOrEmpty(nodeId))
            return nodeId;
        else
            return String.Empty;
    }
    
    public int GetMediaIdByUrl(string url)
    {
        if (!string.IsNullOrWhiteSpace(url))
        {
            var mediaPath = Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Media);
            if (url.StartsWith(mediaPath) && url.Length > mediaPath.Length)
            {
                var parts = url.Substring(mediaPath.Length).Split(new[] { '/', '-' }, StringSplitOptions.RemoveEmptyEntries);
                int propertyId;
    
                if (parts.Length > 1 && int.TryParse(parts[1], out propertyId))
                {
                    return GetContentIdByPropertyId(propertyId);
                }
            }
        }
        return uQuery.RootNodeId;
    }
    
    public int GetContentIdByPropertyId(int propertyId)
    {
        if (propertyId > 0)
        {
            var sqlContentId = "SELECT MAX(contentNodeId) FROM cmsPropertyData WHERE id = @propertyId AND (propertytypeid = 24)"; //24 = media file (umbracoFile)
            Int32 contentId = uQuery.SqlHelper.ExecuteScalar<int>(sqlContentId, uQuery.SqlHelper.CreateParameter("@propertyId", propertyId));
            return contentId;
        }
    
        return uQuery.RootNodeId;
    }
    
    public int GetNodeIdByRequestPath(string path)
    {
        var sqlNodeId = "SELECT MAX(contentNodeId) FROM cmsPropertyData WHERE (DATAnVARCHAR = @path) AND (propertytypeid = 24)";
        Int32 nodeId = uQuery.SqlHelper.ExecuteScalar<int>(sqlNodeId, uQuery.SqlHelper.CreateParameter("@path", path));
        return nodeId;
    }
    
    public static Int32 GetNodeIdFromUrl(String url)
    {
        int nodeId = -1;
        string xpathUrl = url == "/" ? "home" : url;
        string xpath = String.Format("//node[@urlName='{0}']", xpathUrl);
        var node = GetXmlNodeByXPath(xpath);
        if (node != null && node.Count > 0)
        {
            while (node.MoveNext())
            {
                var currentnode = node.Current.GetAttribute("id", "");
                int.TryParse(currentnode, out nodeId);
            }
        }
        return nodeId;
    }
    
    private static XPathNodeIterator GetXmlNodeByXPath(string xpathQuery)
    {
        XPathNavigator xp = content.Instance.XmlContent.CreateNavigator();
        if (xp != null)
            return xp.Select(xpathQuery);
        return null;
    }
    
    //Notice ProcessRequest is the only method exposed by the IHttpHandler
    public void ProcessRequest(HttpContext context)
    {
        //try to get authenticated properties for file
        try
        {
            //local helper declares
            ncba.CRM.Global.AuthenticationHelper authHelper = new ncba.CRM.Global.AuthenticationHelper();
            ncba.CRM.Global.EmailHelper emailHelper = new ncba.CRM.Global.EmailHelper();
    
            //get request URL
            string requestURL = Convert.ToString(context.Request.ServerVariables["SCRIPT_NAME"]).ToLower();
            string msgBody = String.Empty;
    
    
            //get media item
            umbraco.cms.businesslogic.media.Media m = null;
            try
            {
                m = new umbraco.cms.businesslogic.media.Media(GetNodeIdByRequestPath(requestURL));
                if (m.HasProperty("excludeFileFromSearch"))
                {
                    if (m.getProperty("excludeFileFromSearch").Value.ToString() == "1" || m.getProperty("excludeFileFromSearch").Value.ToString() == "true")
                        _excludeFromSearch = true;
                }
            }
            catch { }
    
            //make sure not null
            if (m != null)
            {
                //make sure access item exists
                if (!String.IsNullOrEmpty(Convert.ToString(m.getProperty("itemAccess").Value)))
                {
                    //get roles
                    _allowedRoles = m.getProperty("itemAccess").Value.ToString();
    
                    //force authentication
                    authHelper.ForceAuthentication(_allowedRoles);
                }
            }
    
        }
        catch (System.Exception err)
        {
            throw (err);
        }
    
        //get file request info
        try
        {
            //Map the path to the file
            string strServer = Convert.ToString(context.Request.ServerVariables["SERVER_NAME"]).ToLower();
            string strRequest = Convert.ToString(context.Request.ServerVariables["SCRIPT_NAME"]).ToLower();
            string strMapPath = context.Server.MapPath(strRequest);
    
            bool bOutput = false;
    
            //make sure file exists
            if (File.Exists(strMapPath))
            {
                //open and read file
                string strFileName = Path.GetFileName(strMapPath);
                FileStream MyFileStream = new FileStream(strMapPath, FileMode.Open);
                long FileSize = MyFileStream.Length;
    
                //Allocate size for our buffer array
                byte[] Buffer = new byte[(int)FileSize];
                MyFileStream.Read(Buffer, 0, (int)FileSize);
                MyFileStream.Close();
    
                //Do buffer cleanup
                context.Response.Buffer = true;
                context.Response.Clear();
    
                //set no index header for Google Docs/external search engines
                if (!String.IsNullOrEmpty(_allowedRoles) || _excludeFromSearch)
                    context.Response.AddHeader("X-Robots-Tag", "noindex");
    
                //check file extension  
                if (strRequest.IndexOf(".pdf", 1) > 0)
                {
    
                    //Add the appropriate headers
                    context.Response.AddHeader("content-disposition", "inline; filename=" + strFileName);
    
                    //Add the right contenttype
                    context.Response.ContentType = "application/pdf";
    
                    //set output flag
                    bOutput = true;
    
                }
                else if (strRequest.IndexOf(".doc", 1) > 0)
                {
                    //Add the appropriate headers
                    context.Response.AddHeader("content-disposition", "inline; filename=" + strFileName);
    
                    //Add the right contenttype
                    context.Response.ContentType = "application/doc";
    
                    //set output flag
                    bOutput = true;
                }
                else if (strRequest.IndexOf(".xls", 1) > 0)
                {
                    //Add the appropriate headers
                    context.Response.AddHeader("content-disposition", "inline; filename=" + strFileName);
    
                    //Add the right contenttype
                    context.Response.ContentType = "application/xls";
    
                    //set output flag
                    bOutput = true;
                }
                else if (strRequest.IndexOf(".ppt", 1) > 0)
                {
                    //Add the appropriate headers
                    context.Response.AddHeader("content-disposition", "inline; filename=" + strFileName);
    
                    //Add the right contenttype
                    context.Response.ContentType = "application/ppt";
    
                    //set output flag
                    bOutput = true;
                }
                else if (strRequest.IndexOf(".wpd", 1) > 0)
                {
                    //Add the appropriate headers
                    context.Response.AddHeader("content-disposition", "inline; filename=" + strFileName);
    
                    //Add the right contenttype
                    context.Response.ContentType = "application/wpd";
    
                    //set output flag
                    bOutput = true;
                }
                else if (strRequest.IndexOf(".zip", 1) > 0)
                {
                    //Add the appropriate headers
                    context.Response.AddHeader("content-disposition", "inline; filename=" + strFileName);
    
                    //Add the right contenttype
                    context.Response.ContentType = "application/zip";
    
                    //set output flag
                    bOutput = true;
                }
                else if (strRequest.IndexOf(".txt", 1) > 0)
                {
                    //Add the appropriate headers
                    context.Response.AddHeader("content-disposition", "inline; filename=" + strFileName);
    
                    //Add the right contenttype
                    context.Response.ContentType = "text/plain";
                    //context.Response.ContentType = "application/octet-stream"; 
    
                    //set output flag
                    bOutput = true;
                }
    
                //outfile file
                if (bOutput)
                    context.Response.BinaryWrite(Buffer);
    
            }
            else
            {
                //file does not exists, go to site 404
                context.Response.Redirect("http://" + strServer + "/404.aspx", true);
            }
        }
    
        catch (System.Exception err)
        {
            throw (err);
        }
    }
    
    //By calling IsReusable, an HTTP factory can query a handler to 
    //determine whether the same instance can be used to service multiple requests 
    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
    

    }

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:50
    John Hodgkinson
    0

    our HttpHandler also uses an AuthenticationHelper.cs to check the roles:

    using System;
    

    using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web; using System.Data; using ncba.CRM.WebAccount; using ncba.CRM.Global;

    namespace ncba.CRM.Global { public class AuthenticationHelper { public void ForceAuthentication() { //get web request context System.Web.HttpContext context = System.Web.HttpContext.Current;

            //local helper declares
            ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
    
            //check if logged in user
            User user = w3AccountHelper.GetUserFromCache();
    
            //make sure user exists
            if (user != null)
            {
                //make sure user is NCBA Member
                if (!user.IsUserInRole("NCBA Member"))
                {
                    //create unauthorized URL
                    string unauthorizedUrl = Convert.ToString(System.Configuration.ConfigurationManager.AppSettings["ncba_Apps_UnauthorizedLink"]);
    
                    //redirect to login
                    context.Response.Redirect(unauthorizedUrl, true);
                }
            }
            else
            {
                //create login URL
                string loginUrl = Convert.ToString(System.Configuration.ConfigurationManager.AppSettings["ncba_Apps_LoginLink"]) + "?ReturnUrl=" + context.Request.RawUrl;
    
                //redirect to login
                context.Response.Redirect(loginUrl, true);
            }
        }
    
        public void ForceAuthentication(string role)
        {
            //get web request context
            System.Web.HttpContext context = System.Web.HttpContext.Current;
    
            //local helper declares
            ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
    
            //check if logged in user
            User user = w3AccountHelper.GetUserFromCache();
    
            //make sure user exists
            if (user != null)
            {
                //make sure user is in allowed roles ID list
                if (!user.IsUserInRole(role))
                {
                    //create unauthorized URL
                    string unauthorizedUrl = Convert.ToString(System.Configuration.ConfigurationManager.AppSettings["ncba_Apps_UnauthorizedLink"]);
    
                    //redirect to login
                    context.Response.Redirect(unauthorizedUrl, true);
                }
            }
            else
            {
                //create login URL
                string loginUrl = Convert.ToString(System.Configuration.ConfigurationManager.AppSettings["ncba_Apps_LoginLink"]) + "?ReturnUrl=" + context.Request.RawUrl;
    
                //redirect to login
                context.Response.Redirect(loginUrl, true);
            }
        }
    
        public void ForceAuthentication(string roles, string loginPath, string unauthorizedPath)
        {
            //get web request context
            System.Web.HttpContext context = System.Web.HttpContext.Current;
    
            //local helper declares
            ncba.CRM.WebAccount.Helper w3AccountHelper = new ncba.CRM.WebAccount.Helper();
    
            //check if logged in user
            User user = w3AccountHelper.GetUserFromCache();
    
            //make sure user exists
            if (user != null)
            {
                //make sure user is in allowed roles ID list
                if (!user.IsUserInRole(roles))
                {
                    //create unauthorized URL
                    string unauthorizedUrl = unauthorizedPath;
    
                    //redirect to login
                    context.Response.Redirect(unauthorizedUrl, true);
                }
            }
            else
            {
                //create login URL
                string loginUrl = loginPath + "?ReturnUrl=" + context.Request.RawUrl;
    
                //redirect to login
                context.Response.Redirect(loginUrl, true);
            }
        }
    
        public DataTable GetAuthenticationRoles()
        {
            //local declares
            ncba.CRM.Global.w3CollectionsHelper w3CollectionHelper = new ncba.CRM.Global.w3CollectionsHelper();
    
            //return data from cache/db
            return w3CollectionHelper.Get_Web_Roles();
        }
    
    }
    

    }

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:54
    John Hodgkinson
    0

    this we also add under httphandlers in the web.config:

          <!--MEDIA AUTHENTICATION-->
      <add verb="*" path="*.pdf"  type="HttpRequestIntercept, ncba.MVC" validate="false" />
      <add verb="*" path="*.doc"  type="HttpRequestIntercept, ncba.MVC" validate="false" />
      <add verb="*" path="*.docx" type="HttpRequestIntercept, ncba.MVC" validate="false" />
      <add verb="*" path="*.ppt"  type="HttpRequestIntercept, ncba.MVC" validate="false" />
      <add verb="*" path="*.xls"  type="HttpRequestIntercept, ncba.MVC" validate="false" />
      <add verb="*" path="*.wpd"  type="HttpRequestIntercept, ncba.MVC" validate="false" />
      <add verb="*" path="*.zip"  type="HttpRequestIntercept, ncba.MVC" validate="false" />
      <add verb="*" path="*.txt"  type="HttpRequestIntercept, ncba.MVC" validate="false" />
      <add verb="*" path="*.mobi" type="HttpRequestIntercept, ncba.MVC" validate="false" />
      <add verb="*" path="*.epub" type="HttpRequestIntercept, ncba.MVC" validate="false" />  
      <add verb="*" path="*.pdb"  type="HttpRequestIntercept, ncba.MVC" validate="false" />     
    

    and also under handlers in the system.webServer are of the web.config:

      <!--MEDIA HANDLERS-->
      <add name="PDF" path="*.pdf" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
      <add name="DOC" path="*.doc" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
      <add name="DOCX" path="*.docx" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
      <add name="PPT" path="*.ppt" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
      <add name="XLS" path="*.xls" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
      <add name="WPD" path="*.wpd" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
      <add name="ZIP" path="*.zip" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
      <add name="TXT" path="*.txt" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
      <add name="EPUB" path="*.epub" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
      <add name="MOBI" path="*.mobi" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
      <add name="PDB" path="*.pdb" verb="*" type="HttpRequestIntercept, ncba.MVC" resourceType="Unspecified" />
    
  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 11:57
    John Hodgkinson
    0

    then upload a file and then set it's access roles and it works just the content page access functionality:

    enter image description here

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 12:02
    John Hodgkinson
    0

    on the MVC controller end, we then have a CustomAuthorize.cs class that inherits from the AuthorizeAttribute class. we've also wired up properties AllowAll, AllowUsers, AllowUserTypes, AllowMembers and AllowMemberGroups so that we can check both users and members and also allow anyone to access directory by decorating the Controller class. If Authentication fails we also pass back an Unauthorized Status back the the MemberLogin view so that it will reidrect to an unauthorized page instead of redirecting back to the login:

    #region namespace
    

    using System; using System.Security.Principal; using System.Web; using System.Web.Mvc; using System.Web.Security; using Umbraco.Core; using umbraco; using umbraco.cms; using System.Text; using System.Collections.Generic; using System.Configuration; using System.Linq; using umbraco.cms.businesslogic.member; using AuthorizeAttribute = System.Web.Mvc.AuthorizeAttribute; using Umbraco.Web;

    endregion

    namespace ncba.MVC.CustomProviders { public class UmbracoAuthorizeAttribute : AuthorizeAttribute { #region Local Variables private readonly UmbracoContext _umbracoContext; #endregion

        #region Properties
        /// <summary>
        /// Flag for whether to allow all site visitors or just authenticated members
        /// </summary>
        /// <remarks>
        /// This is the same as applying the [AllowAnonymous] attribute
        /// </remarks>
        public bool AllowAll { get; set; }
    
        /// <summary>
        /// Comma delimited list of allowed back office users
        /// </summary>
        public string AllowUsers { get; set; }
    
        /// <summary>
        /// Comma delimited list of allowed user back office types
        /// </summary>
        public string AllowUserTypes { get; set; }
    
        /// <summary>
        /// Comma delimited list of allowed front end members
        /// </summary>
        public string AllowMembers { get; set; }
    
        /// <summary>
        /// Comma delimited list of allowed front end member groups
        /// </summary>
        public string AllowMemberGroups { get; set; }
    
        /// <summary>
        /// Authentication flag for all checks
        /// </summary>
        private bool failedAuthentication = true;
        #endregion
    
        #region Methods
        private UmbracoContext GetUmbracoContext()
        {
            return _umbracoContext ?? UmbracoContext.Current;
        }
    
        public UmbracoAuthorizeAttribute(ApplicationContext appContext)
        {
            if (appContext == null) throw new ArgumentNullException("appContext");
        }
    
        public UmbracoAuthorizeAttribute()
            : this(ApplicationContext.Current)
        {
        }
    
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException("httpContext");
            }
            try
            {
                //authentication check for back office user account
                if (!String.IsNullOrEmpty(AllowUsers))
                {
                    //make sure user logged into back office
                    if (UmbracoContext.Current.UmbracoUser != null)
                    {
                        //get current back office user
                        umbraco.BusinessLogic.User boUser = UmbracoContext.Current.UmbracoUser; 
    
                        //split allowed back office users
                        string[] allowedUsers = AllowUsers.Split(',');
    
                        //check if current back office user in allowed list
                        if(allowedUsers.Contains(boUser.Name))
                            failedAuthentication = false;
                    }
                }
    
                //authentication check for back office user account types
                if (!String.IsNullOrEmpty(AllowUserTypes))
                {
                    //make sure user logged into back office
                    if (UmbracoContext.Current.UmbracoUser != null)
                    {
                        //get current back office user
                        umbraco.BusinessLogic.User boUser = UmbracoContext.Current.UmbracoUser;
    
                        //split allowed back office user types
                        string[] allowedUserTypes = AllowUserTypes.Split(',');
    
                        //check if current back office user in allow group
                        if (allowedUserTypes.Contains(boUser.UserType.Name))
                            failedAuthentication = false;
                    }
                }
    
                //authentication check for front end member accounts
                if (!String.IsNullOrEmpty(AllowMembers))
                {
                    //get .NET Member account
                    MembershipUser member = Membership.GetUser();
    
                    //check if member logged in
                    if (member != null)
                    {
                        //split allowed roles
                        string[] allowedMembers = AllowMemberGroups.Split(',');
    
                        //if member in member group, set flag
                        if(allowedMembers.Contains(member.UserName))
                            failedAuthentication = false;
                    }
                }
    
                //authentication check for front end member account groups
                if (!String.IsNullOrEmpty(AllowMemberGroups))
                {
                    //get .NET Member account
                    MembershipUser member = Membership.GetUser();
    
                    //check if member logged in
                    if (member != null)
                    {
                        //split allowed roles
                        string[] allowedGroups = AllowMemberGroups.Split(',');
    
                        //iterate allowed groups and check if member
                        foreach (var group in allowedGroups)
                        {
                            //if member in role, set flag
                            if(System.Web.Security.Roles.IsUserInRole(group))
                                failedAuthentication = false;
                        }
                    }
    
                }
    
                //check for allow all (anonymous)
                if (AllowAll)
                    failedAuthentication = false;
    
                //return back authentication result
                return !failedAuthentication;
            }
            catch (Exception err)
            {
                var error = err.Message + err.Source + err.StackTrace;
    
                return false;
            }
        }
    
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            base.OnAuthorization(filterContext);
    
            //if failed autication
            if (failedAuthentication)
            {
                //get http context
                HttpContextBase context = filterContext.RequestContext.HttpContext;
    
                //check if user authenticated
                if (context.User.Identity.IsAuthenticated)
                {
                    // add unauthorized message
                    filterContext.Controller.TempData.Add("Status", "Unauthorized Access");
                }
                else
                {   //user not authenticated, send to login
                    FormsAuthentication.RedirectToLoginPage();
                }
            }
        }
        #endregion
    }
    

    }

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 12:04
    John Hodgkinson
    0

    and setting the the authentication via the surface controller:

    enter image description here

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 12:08
    John Hodgkinson
    0

    and the unauthorized status check in the login that may be returned back from the Authorize class:

    enter image description here

  • John Hodgkinson 613 posts 355 karma points
    Apr 22, 2014 @ 12:11
    John Hodgkinson
    0

    And I think that's it... This was my first crack at creating a custom membership and role provider and also hookup authentication on the MVC controller end of things with an external CRM so please let me know if so see things I can improve or make more efficient. Many thanks!

Please Sign in or register to post replies

Write your reply to:

Draft