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...
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
}
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
}
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; }
}
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("/");
}
}
@{
//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>
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();
}
}
}
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;
}
}
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();
}
}
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
}
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!
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...
CustomMembershipProivder.cs
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
}
CustomMemberRoleProvider.cs
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
}
then hookup in web.config under system.web:
our MemberLogin Model:
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; }
}
our MemberLogin Controller:
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 {
}
our MemberLogin view:
@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); }
}
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):
attempting to access authenticated page:
currently a member of one of the allowed roles, so we're in:
If we not a member of one of the allowed roles, we're denied access:
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.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;
}
hooked up the new data type into the back office and exposed it to our media file:
and then create a new property for our media file type using the above data type:
the we created the HttpRequestIntercept.cs HttpHandler for file extension types we wanted to protect:
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;
}
our HttpHandler also uses an AuthenticationHelper.cs to check the roles:
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;
}
this we also add under httphandlers in the web.config:
and also under handlers in the system.webServer are of the web.config:
then upload a file and then set it's access roles and it works just the content page access functionality:
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:
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
}
and setting the the authentication via the surface controller:
and the unauthorized status check in the login that may be returned back from the Authorize class:
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!
is working on a reply...