Extranet Basic
Created by Orckestra
Extranet Developer Guide
- Introduction
- Writing Custom Extranet Providers
- Using Extranet Facade Methods
- About Extranet Security
- Extranet Functions
Writing Custom Extranet Providers
By default, the Extranet add-on uses a built-in extranet provider. When you first open the Extranet perspective, it has neither users nor groups. Normally, you can add them manually and then assign users to groups or vice versa.
However, you are not limited to only one – default - extranet provider. For example, if you have your own database (an ERP or CRM system) with users you want to be your extranet users (customers, suppliers etc), you can create your own extranet provider that will get the users from this database, assign them to specific groups and use them instead of, or along with, the default extranet provider.
The Extranet add-on comes with one default extranet provider:
- DefaultExtranetProvider
The following assembly contains the code for this provider:
- Composite.Community.Extranet.dll
This assembly is installed in the Bin folder on an C1 CMS-based website.
The provider is plugged in via the C1 CMS configuration file (\\<website>\Apps_Data\Composite\Composite.config) in the < Composite.Community.Extranet.Plugins.ExtranetProviderConfiguration> section:
<Composite.Community.Extranet.Plugins.ExtranetProviderConfiguration> <ExtranetProviderPlugins> <add name="Default" title="Extranet" type="Composite.Community.Extranet.DefaultProvider.DefaultExtranetProvider, Composite.Community.Extranet" /> </ExtranetProviderPlugins> </Composite.Community.Extranet.Plugins.ExtranetProviderConfiguration>
Listing 1: Default provider plugged in
Each <add> element stands for one extranet provider and has three mandatory attributes:
- name
- title
- type
The name attribute specifies the name of the extranet provider (This is the name by which the provider is referred to in the code.)
The title attribute specifies the title of the extranet provider. (This is the name by which the user identifies the extranet in the GUI).
The type attribute specifies the name of the extranet provider class followed by the provider’s assembly separated by a comma.
In the default configuration, these values are as follows:
- Name: Default
- Title: Extranet
- Provider Class: Composite.Community.Extranet.DefaultProvider.DefaultExtranetProvider
- Assembly: Composite.Community.Extranet
Using the plug-in model of The Extranet add-on, you can create custom extranet providers, integrate them into The Extranet add-on and use them on your websites.
These providers can get users and, possibly, groups, from external databases, for example, an ERP/CRM system or an SQL database.
Creating a custom provider requires creating an assembly (similar to the default one), which implements the plug-in model related classes and interfaces and plug it in to the Extranet add-on.
The steps to create a custom extranet provider include:
- Creating a class library project and adding required references to it.
- Creating required classes by implementing the Extranet plug-in model.
- Building the provider and deploying it on a website.
In the following few sections, you will learn more about each of these steps.
For illustration, we will create a sample custom provider called “Simple Extranet Provider”.
To simplify the sample code, instead of using 3rd-party application APIs to retrieve users and groups from external sources, we will generate them in the custom provider on-the-fly and use them in our methods.
Each custom extranet provider is represented by a class library assembly. So you should start by creating a class library project by using a Class Library project template in Visual Studio 2008 (Visual C#, Windows, Class Library).
For our sample we will create the project called SimpleExtranetProvider.
To be able to use C1 CMS, The Extranet add-on and other functionality, you should add a number of references to the project.Normally, you should add references to some of the assemblies located in the Bin folder of your website with the Extranet add-on already installed.
The following references must be added to the project (in addition to the default ones):
- Composite
- Composite.Community.Extranet
- Microsoft.Practices.EnterpriseLibrary.Common
- Microsoft.Practices.EnterpriseLibrary.Configuration.Design
- Microsoft.Practices.ObjectBuilder
- System.Configuration
Once you have created a Class Library project and added all required references, go on to create required classes for your extranet provider.
Before we start taking the steps to actually create a custom extranet provider, we have created a very simple “external” (dummy) source that we will use in our sample code to store and retrieve information about our users, groups and users-to-groups relations. You should use your own code to manage your external sources for this purpose.
Creating Simple External Source for Sample Code
For our sample code, we have created a simple “external” source to fill our extranet with new users and groups and store them in. This code is presented here for illustration only.
When you create your extranet provider, you will use a different source for your extranet members and should skip this step altogether.
In the extranet provider class that we will create shortly, we have two private lists to hold the information about the users and groups respectively that will serve as our “simple external source”.
private List<ExtranetUser> _users; private List<ExtranetGroup> _groups;
We also have two dictionaries:
private Dictionary<Guid, List<Guid>> _groupUsers; private Dictionary<string, string> _userName;
The first one holds relations between users and groups for us, the second one maps a username to a user password. The latter is absolutely unsafe in the real-life code. However, we use this simplistic approach for illustration only.
We have created a private method which takes care of initialization of those private variables and called it from the extranet provider class’s constructor to have our simple “external” source up and running.
private void CreateSimpleExternalSource() { // generate 20 users _users = (from u in Enumerable.Range(1, 20) select new ExtranetUser { Id = Guid.NewGuid(), CreationDate = new DateTime(2009, 1, 1).AddDays(u), IsApproved = true, UserName = "User" + (u.ToString().Length > 1 ? "" : "0") + u.ToString(), Email = "user" + (u.ToString().Length > 1 ? "" : "0") + u.ToString() + "@dummymail.net", Name = "User " + (u.ToString().Length > 1 ? "" : "0") + u.ToString(), FolderName = (u % 2 == 0) ? "Domestic Companies" : "Foreign Companies", ProviderName = "Simple" }).ToList(); // set passwords _userName = new Dictionary<string, string>(); foreach (ExtranetUser user in _users) { _userName.Add(user.UserName, user.Name); } // create groups _groups = new List<ExtranetGroup>(); // create the root group ExtranetGroup group = new ExtranetGroup(); group.Id = Guid.NewGuid(); group.Name = "All Groups"; group.ParentGroupId = null; group.CanBeAssignedAccessRight = false; group.ProviderName = "Simple"; Guid rootGroupId = group.Id; _groups.Add(group); // create the Customers sub-group group = new ExtranetGroup(); group.Id = Guid.NewGuid(); group.Name = "Customers"; group.ParentGroupId = rootGroupId; group.CanBeAssignedAccessRight = true; group.MemberUsersUpdatable = true; group.ProviderName = "Simple"; _groups.Add(group); // create the Suppliers sub-group group = new ExtranetGroup(); group.Id = Guid.NewGuid(); group.Name = "Suppliers"; group.ParentGroupId = rootGroupId; group.CanBeAssignedAccessRight = true; group.MemberUsersUpdatable = true; group.ProviderName = "Simple"; _groups.Add(group); // assign users to groups _groupUsers = new Dictionary<Guid, List<Guid>>(); List<ExtranetUser>.Enumerator userEnum = _users.GetEnumerator(); foreach (ExtranetGroup pg in _groups.Where(f => f.CanBeAssignedAccessRight == true)) { List<Guid> userIds = new List<Guid>(); for (int i = 0; i < 10; i++) { userEnum.MoveNext(); userIds.Add(userEnum.Current.Id); } _groupUsers.Add(pg.Id, userIds); } }
Listing 2: Generating a dummy source to populate the custom extranet
In the example above:
- We generate 20 user accounts by setting the properties of the ExtranetUser class instances and adding these instances to the _users list. We name the users as “User01” to “User20”, set their “dummy” email addresses and place them in one of the two folders: “Domestic Companies” and “Foreign Companies”.
- Then we set their passwords by using our _userName dictionary.
- We continue to create one root group (“All Groups”) and two sub-groups under this group – “Customers” and “Suppliers” - by using ExtranetGroup and adding their instances to the _groups list.
- Finally, we assign one half of the user accounts to the Customers group and the other to the Suppliers group by using _groupUsers.
This is the structure of user accounts and user groups that will appear in the Extranet perspective out-of-the-box when we eventually build and deploy our sample Simple Extranet Provider.
To integrate your extranet provider into the Extranet add-on, you should create a class that will represent this provider.
Based on your requirements to a custom provider you can choose to implement one or more interfaces when creating this class. All the interfaces that you should or might implement are located in the following namespace:
- Composite.Community.Extranet.Plugins.ExtranetProvider
You should at least implement one interface: IExtranetProvider.This interface provides the basic functionality needed for a custom provider to work. This interface gives read-only access to the external extranet user accounts and groups as well as the users-to-groups assignments.
To be able to edit these user accounts, groups and the relations between them, you should implement a number of other interfaces, which we will shortly cover.
Let’s start with creating the custom extranet provider class by implementing this interface and then proceed to implementation of other useful interfaces.
Step 1: Implementing IExtranetProvider Interface
First of all, you should create the class for your custom extranet provider.
This class should at minimum:
- Provide the title of the extranet provider
- Retrieve a user by ID
- Retrieve the user ID by name
- Retrieve all the users
- Retrieve a group
- Retrieve group IDs assigned to one user
- Retrieve users assigned to one group
- Get the hierarchy of a group
- Lock out a user
- Set a user active
To create the extranet provider class, you should implement at least one interface: IExtranetProvider.
public interface IExtranetProvider { string ProviderTitle { get; } Guid GetUserId(string userName); IExtranetUser GetUser(Guid userId); IEnumerable<IExtranetUser> GetAllUsers(); IEnumerable<Guid> GetGroupIdListForUser(Guid userId); IEnumerable<IExtranetGroupHierarchyNode> GetGroupHierarchy(); IExtranetGroup GetGroup(Guid groupId); IEnumerable<IExtranetUser> GetUsersFromGroup(Guid groupId); void SetUserIsLockedOut(Guid userId, bool lockedOut); void UserActivity(Guid userId); }
Listing 3: IExtranetProvider interface
In the following example, we have created the SimpleExtranetProvider for our sample solution by implementing the IExtranetProvider interface. This sample also makes use of our simple “external” source we have created earlier for illustration to populate our custom extranet.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Composite.Community.Extranet.Plugins.ExtranetProvider; using Composite.Community.Extranet.Data; using Composite.Community.Extranet.DefaultProvider; using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder; using System.Configuration; using Microsoft.Practices.ObjectBuilder; using Composite.Community.Extranet; using Composite.Renderings.Page; namespace SimpleExtranetProvider { [ConfigurationElementType(typeof(SimpleExtranetProviderConfiguration))] public class SimpleExtranetProvider : IExtranetProvider { private string _providerTitle = null; private List<ExtranetUser> _users; private List<ExtranetGroup> _groups; private Dictionary<Guid, List<Guid>> _groupUsers; private Dictionary<string, string> _userName; public SimpleExtranetProvider() : this("Simple Extranet") { } public SimpleExtranetProvider(string providerTitle) { _providerTitle = providerTitle; CreateSimpleExternalSource(); } #region IExtranetProvider Members public string ProviderTitle { get { return _providerTitle; } } public Guid GetUserId(string userName) { IExtranetUser user = _users.Where(f => f.UserName == userName).FirstOrDefault(); if (user == null) return Guid.Empty; else return user.Id; } public IExtranetUser GetUser(Guid userId) { return _users.Where(f => f.Id == userId).FirstOrDefault(); } public IEnumerable<IExtranetUser> GetAllUsers() { return _users.Select(f => f as IExtranetUser); } public IEnumerable<Guid> GetGroupIdListForUser(Guid userId) { foreach (Guid groupId in _groupUsers.Keys) { if (_groupUsers[groupId].Contains(userId)) yield return groupId; } } public IEnumerable<IExtranetGroupHierarchyNode> GetGroupHierarchy() { foreach (ExtranetGroup gp in _groups.Where(f => f.ParentGroupId == null)) { ExtranetGroupHierarchyNode ghn = new ExtranetGroupHierarchyNode(gp); ghn.Groups = GetSubGroupHierarchy(gp.Id); yield return ghn; } } private IEnumerable<IExtranetGroupHierarchyNode> GetSubGroupHierarchy(Guid groupId) { foreach (ExtranetGroup gp in _groups.Where(f => f.ParentGroupId == groupId)) { ExtranetGroupHierarchyNode ghn = new ExtranetGroupHierarchyNode(gp); ghn.Groups = GetSubGroupHierarchy(gp.Id); yield return ghn; } } public IExtranetGroup GetGroup(Guid groupId) { return _groups.Where(f => f.Id == groupId).First(); } public IEnumerable<IExtranetUser> GetUsersFromGroup(Guid groupId) { if (_groupUsers.ContainsKey(groupId)) { return _groupUsers[groupId].Select(f => _users.Where(n => n.Id == f).First() as IExtranetUser); } else { return new List<IExtranetUser>(); } } public void SetUserIsLockedOut(Guid userId, bool lockedOut) { this._users[this._users.FindIndex(f => f.Id == userId)].IsLockedOut = lockedOut; this._users[this._users.FindIndex(f => f.Id == userId)].LastLockedOutDate = DateTime.Now; } public void UserActivity(Guid userId) { this._users[this._users.FindIndex(f => f.Id == userId)].LastActivityDate = DateTime.Now; } #endregion #region External Source private void CreateSimpleExternalSource() { // generate 20 users _users = (from u in Enumerable.Range(1, 20) select new ExtranetUser { Id = Guid.NewGuid(), CreationDate = new DateTime(2009, 1, 1).AddDays(u), IsApproved = true, UserName = "User" + (u.ToString().Length > 1 ? "" : "0") + u.ToString(), Email = "user" + (u.ToString().Length > 1 ? "" : "0") + u.ToString() + "@dummymail.net", Name = "User " + (u.ToString().Length > 1 ? "" : "0") + u.ToString(), FolderName = (u % 2 == 0) ? "Domestic Companies" : "Foreign Companies", ProviderName = "Simple" }).ToList(); // set passwords _userName = new Dictionary<string, string>(); foreach (ExtranetUser user in _users) { _userName.Add(user.UserName, user.Name); } // create groups _groups = new List<ExtranetGroup>(); // create the root group ExtranetGroup group = new ExtranetGroup(); group.Id = Guid.NewGuid(); group.Name = "All Groups"; group.ParentGroupId = null; group.CanBeAssignedAccessRight = false; group.ProviderName = "Simple"; Guid rootGroupId = group.Id; _groups.Add(group); // create the Customers sub-group group = new ExtranetGroup(); group.Id = Guid.NewGuid(); group.Name = "Customers"; group.ParentGroupId = rootGroupId; group.CanBeAssignedAccessRight = true; group.MemberUsersUpdatable = true; group.ProviderName = "Simple"; _groups.Add(group); // create the Suppliers sub-group group = new ExtranetGroup(); group.Id = Guid.NewGuid(); group.Name = "Suppliers"; group.ParentGroupId = rootGroupId; group.CanBeAssignedAccessRight = true; group.MemberUsersUpdatable = true; group.ProviderName = "Simple"; _groups.Add(group); // assign users to groups _groupUsers = new Dictionary<Guid, List<Guid>>(); List<ExtranetUser>.Enumerator userEnum = _users.GetEnumerator(); foreach (ExtranetGroup pg in _groups.Where(f => f.CanBeAssignedAccessRight == true)) { List<Guid> userIds = new List<Guid>(); for (int i = 0; i < 10; i++) { userEnum.MoveNext(); userIds.Add(userEnum.Current.Id); } _groupUsers.Add(pg.Id, userIds); } } #endregion } [Assembler(typeof(SimpleExtranetProviderAssembler))] public class SimpleExtranetProviderConfiguration : ExtranetProviderData { private const string _titleProperty = "title"; [ConfigurationProperty(_titleProperty, IsRequired = true)] public string Title { get { return (string)base[_titleProperty]; } } } internal sealed class SimpleExtranetProviderAssembler : IAssembler<IExtranetProvider, ExtranetProviderData> { public IExtranetProvider Assemble(IBuilderContext context, ExtranetProviderData objectConfiguration, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache) { SimpleExtranetProviderConfiguration config = objectConfiguration as SimpleExtranetProviderConfiguration; SimpleExtranetProvider provider = new SimpleExtranetProvider(config.Title); return provider; } } }
Listing 4: Implementing IExtranetProvider in the sample class
As you can see in the example above:
- For our simple extranet provider, in the constructor we call CreateSimpleExtrenalSource method that populates our custom extranet with user accounts, groups etc.
- Each implemented method returns the required value by retrieving it from our “external” source.
- In the constructor, we also initialize the provider’s title retrieved from the configuration file.
- Since the provider’s title is retrieved from the Composite.config file, we implement the required logic to get the provider’s title (the ConfigurationElementType attribute and the SimpleExtranetProviderConfiguration class).
This sample code is self-sufficient. If you build, deploy and plug it in, you will have a working instance of a custom extranet provider in read-only mode. You will be able to only view the extranet from the Extranet perspective and use it on your websites and media.
We will go a little further and implement a few other interfaces to allow administrators to add, delete and modify users and groups, assign the users to groups and groups to users, change passwords and validate users on extranet protected websites and media by granting or denying access to web pages and media files.
Step 2: Implementing IExtranetUserUpdateProvider Interface
To allow extranet administrators to add, modify and delete extranet user accounts from the CMS Console, you should implement the IExtranetUserUpdateProvider interface.
By implementing this interface in your custom extranet provider class, you also enable the administrators to assign users to groups and groups to users as well as (re)set the users’ passwords.
public interface IExtranetUserUpdateProvider { IExtranetUser AddNewUser(IExtranetUser user); void UpdateUser(IExtranetUser user); void SetGroupsForUser(Guid userId, IEnumerable<Guid> groupIdList); void SetUsersForGroup(Guid groupId, IEnumerable<Guid> userIdList); bool SetUserPassword(Guid userId, string newPassword); void DeleteUser(Guid userId); }
Listing 5: IExtranetUserUpdateProvider interface
Step 3: Implementing IExtranetGroupUpdateProvider Interface
To allow extranet administrators to add, modify and delete extranet user groups, you should implement the IExtranetGroupUpdateProvider interface.
public interface IExtranetGroupUpdateProvider { IExtranetGroup AddNewGroup(IExtranetGroup group); void UpdateGroup(IExtranetGroup group); void DeleteGroup(Guid groupId); }
Listing 6: IExtranetGroupUpdateProvider interface
Step 4: Implementing IExtranetUserPasswordProvider Interface
Allowing users to restore their forgotten passwords via email requires implementation of the IExtranetUserPasswordProvider interface.
public interface IExtranetUserPasswordProvider { string GetUserPassword(string login); }
Listing 7: IExtranetUserPasswordProvider interface
Step 5: Implementing IExtranetFormsLoginProvider Interface
To validate users on protected website resources, you should implement the IExtranetFormsLoginProvider interface.
public interface IExtranetFormsLoginProvider { bool ValidateUser(string login, string password, out Guid userId); }
Listing 8: IExtranetFormsLoginProvider interface
Finalizing Simple Extranet Provider Class
In the following example, we have additionally implemented the IExtranetUserUpdateProvider, IExtranetGroupUpdateProvider, IExtranetUserPasswordProvider and IExtranetFormsLoginProvider interfaces in our SimpleExtranetProvider class.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Composite.Community.Extranet.Plugins.ExtranetProvider; using Composite.Community.Extranet.Data; using Composite.Community.Extranet.DefaultProvider; using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder; using System.Configuration; using Microsoft.Practices.ObjectBuilder; using Composite.Community.Extranet; using Composite.Renderings.Page; namespace SimpleExtranetProvider { [ConfigurationElementType(typeof(SimpleExtranetProviderConfiguration))] public class SimpleExtranetProvider : IExtranetProvider, IExtranetFormsLoginProvider, IExtranetUserPasswordProvider, IExtranetUserUpdateProvider, IExtranetGroupUpdateProvider { private string _providerTitle = null; private List<ExtranetUser> _users; private List<ExtranetGroup> _groups; private Dictionary<Guid, List<Guid>> _groupUsers; private Dictionary<string, string> _userName; public SimpleExtranetProvider() : this("Simple Extranet") { } public SimpleExtranetProvider(string providerTitle) { _providerTitle = providerTitle; CreateSimpleExternalSource(); } #region IExtranetProvider Members public string ProviderTitle { get { return _providerTitle; } } public Guid GetUserId(string userName) { IExtranetUser user = _users.Where(f => f.UserName == userName).FirstOrDefault(); if (user == null) return Guid.Empty; else return user.Id; } public IExtranetUser GetUser(Guid userId) { return _users.Where(f => f.Id == userId).FirstOrDefault(); } public IEnumerable<IExtranetUser> GetAllUsers() { return _users.Select(f => f as IExtranetUser); } public IEnumerable<Guid> GetGroupIdListForUser(Guid userId) { foreach (Guid groupId in _groupUsers.Keys) { if (_groupUsers[groupId].Contains(userId)) yield return groupId; } } public IEnumerable<IExtranetGroupHierarchyNode> GetGroupHierarchy() { foreach (ExtranetGroup gp in _groups.Where(f => f.ParentGroupId == null)) { ExtranetGroupHierarchyNode ghn = new ExtranetGroupHierarchyNode(gp); ghn.Groups = GetSubGroupHierarchy(gp.Id); yield return ghn; } } private IEnumerable<IExtranetGroupHierarchyNode> GetSubGroupHierarchy(Guid groupId) { foreach (ExtranetGroup gp in _groups.Where(f => f.ParentGroupId == groupId)) { ExtranetGroupHierarchyNode ghn = new ExtranetGroupHierarchyNode(gp); ghn.Groups = GetSubGroupHierarchy(gp.Id); yield return ghn; } } public IExtranetGroup GetGroup(Guid groupId) { return _groups.Where(f => f.Id == groupId).First(); } public IEnumerable<IExtranetUser> GetUsersFromGroup(Guid groupId) { if (_groupUsers.ContainsKey(groupId)) { return _groupUsers[groupId].Select(f => _users.Where(n => n.Id == f).First() as IExtranetUser); } else { return new List<IExtranetUser>(); } } public void SetUserIsLockedOut(Guid userId, bool lockedOut) { this._users[this._users.FindIndex(f => f.Id == userId)].IsLockedOut = lockedOut; this._users[this._users.FindIndex(f => f.Id == userId)].LastLockedOutDate = DateTime.Now; } public void UserActivity(Guid userId) { this._users[this._users.FindIndex(f => f.Id == userId)].LastActivityDate = DateTime.Now; } #endregion #region IExtranetUserPasswordProvider Members public string GetUserPassword(string login) { return _userName[login]; } #endregion #region IExtranetFormsLoginProvider Members public bool ValidateUser(string login, string password, out Guid userId) { userId = Guid.Empty; if (_userName.ContainsKey(login)) { if (_userName[login] == password) { userId = _users.Where(f => f.UserName == login).FirstOrDefault().Id; Guid pageId = PageRenderer.CurrentPageId; IEnumerable<Guid> userGroupIds = GetGroupIdListForUser(userId); IEnumerable<Guid> pageGroupIds = ExtranetFacade.GetPageSecurityGroups("Simple", pageId); if (ExtranetFacade.ValidateUserGroupsToItemGroups("Simple", userGroupIds, pageGroupIds)) { Guid lampdaUserId = userId; _users[_users.FindIndex(f => f.Id == lampdaUserId)].LastLoginDate = DateTime.Now; _users[_users.FindIndex(f => f.Id == lampdaUserId)].LastActivityDate = DateTime.Now; return true; } } } return false; } #endregion #region IExtranetUserUpdateProvider Members public IExtranetUser AddNewUser(IExtranetUser user) { _users.Add(user as ExtranetUser); return user; } public void UpdateUser(IExtranetUser user) { _users[_users.FindIndex(f => f.Id == user.Id)] = (ExtranetUser)user; } public void DeleteUser(Guid userId) { ExtranetUser user = _users.Where(f => f.Id == userId).First(); _userName.Remove(user.UserName); _users.Remove(user); foreach (List<Guid> userlist in _groupUsers.Values) { userlist.Remove(userId); } } public bool SetUserPassword(Guid userId, string newPassword) { try { _userName[_users.Where(f => f.Id == userId).First().UserName] = newPassword; _users[_users.FindIndex(f => f.Id == userId)].LastPasswordChangeDate = DateTime.Now; return true; } catch (Exception) { return false; } } public void SetGroupsForUser(Guid userId, IEnumerable<Guid> groupIdList) { foreach (Guid groupId in _groupUsers.Keys) { if (_groupUsers[groupId].Contains(userId)) _groupUsers[groupId].Remove(userId); } foreach (Guid groupId in groupIdList) { if (_groupUsers.ContainsKey(groupId)) _groupUsers[groupId].Add(userId); else _groupUsers.Add(groupId, new List<Guid>() { userId }); } } public void SetUsersForGroup(Guid groupId, IEnumerable<Guid> userIdList) { if (_groupUsers.ContainsKey(groupId)) _groupUsers[groupId] = userIdList.ToList(); else _groupUsers.Add(groupId, userIdList.ToList()); } #endregion #region IExtranetGroupUpdateProvider Members public IExtranetGroup AddNewGroup(IExtranetGroup group) { _groups.Add(new ExtranetGroup(group)); return group; } public void DeleteGroup(Guid groupId) { _groups.RemoveAll(f => f.Id == groupId); _groupUsers.Remove(groupId); } public void UpdateGroup(IExtranetGroup group) { _groups[_groups.FindIndex(f => f.Id == group.Id)] = new ExtranetGroup(group); } #endregion #region External Source private void CreateSimpleExternalSource() { // generate 20 users _users = (from u in Enumerable.Range(1, 20) select new ExtranetUser { Id = Guid.NewGuid(), CreationDate = new DateTime(2009, 1, 1).AddDays(u), IsApproved = true, UserName = "User" + (u.ToString().Length > 1 ? "" : "0") + u.ToString(), Email = "user" + (u.ToString().Length > 1 ? "" : "0") + u.ToString() + "@dummymail.net", Name = "User " + (u.ToString().Length > 1 ? "" : "0") + u.ToString(), FolderName = (u % 2 == 0) ? "Domestic Companies" : "Foreign Companies", ProviderName = "Simple" }).ToList(); // set passwords _userName = new Dictionary<string, string>(); foreach (ExtranetUser user in _users) { _userName.Add(user.UserName, user.Name); } // create groups _groups = new List<ExtranetGroup>(); // create the root group ExtranetGroup group = new ExtranetGroup(); group.Id = Guid.NewGuid(); group.Name = "All Groups"; group.ParentGroupId = null; group.CanBeAssignedAccessRight = false; group.ProviderName = "Simple"; Guid rootGroupId = group.Id; _groups.Add(group); // create the Customers sub-group group = new ExtranetGroup(); group.Id = Guid.NewGuid(); group.Name = "Customers"; group.ParentGroupId = rootGroupId; group.CanBeAssignedAccessRight = true; group.MemberUsersUpdatable = true; group.ProviderName = "Simple"; _groups.Add(group); // create the Suppliers sub-group group = new ExtranetGroup(); group.Id = Guid.NewGuid(); group.Name = "Suppliers"; group.ParentGroupId = rootGroupId; group.CanBeAssignedAccessRight = true; group.MemberUsersUpdatable = true; group.ProviderName = "Simple"; _groups.Add(group); // assign users to groups _groupUsers = new Dictionary<Guid, List<Guid>>(); List<ExtranetUser>.Enumerator userEnum = _users.GetEnumerator(); foreach (ExtranetGroup pg in _groups.Where(f => f.CanBeAssignedAccessRight == true)) { List<Guid> userIds = new List<Guid>(); for (int i = 0; i < 10; i++) { userEnum.MoveNext(); userIds.Add(userEnum.Current.Id); } _groupUsers.Add(pg.Id, userIds); } } #endregion } [Assembler(typeof(SimpleExtranetProviderAssembler))] public class SimpleExtranetProviderConfiguration : ExtranetProviderData { private const string _titleProperty = "title"; [ConfigurationProperty(_titleProperty, IsRequired = true)] public string Title { get { return (string)base[_titleProperty]; } } } internal sealed class SimpleExtranetProviderAssembler : IAssembler<IExtranetProvider, ExtranetProviderData> { public IExtranetProvider Assemble(IBuilderContext context, ExtranetProviderData objectConfiguration, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache) { SimpleExtranetProviderConfiguration config = objectConfiguration as SimpleExtranetProviderConfiguration; SimpleExtranetProvider provider = new SimpleExtranetProvider(config.Title); return provider; } } }
Listing 9: Implementing four other interfaces in the sample class
In the example above:
- We implement methods of all the four interfaces and thus extend our custom provider’s capabilities. In each case, we simply use our built-in simple “external” source to manage user accounts, groups, passwords and users-to-groups relations.
- In the ValidateUser method called when a user tries to access protected resources, we not only check if the user account and the password are valid but also check the user account’s access permissions to the current page.
- We do the latter by getting the list of groups the user is member of and the list of groups allowed on the resource (a page, in our case) and then calling the ExtranetFacade’s method ValidateUserGroupsToItemGroups passing the lists as its parameters
- Unlike our sample code, the real code should additionally validate the user’s groups against the so-called inherited groups by using the ExtranetFacade’s GetPageSecurityInheritedGroups method along with GetPageSecurityGroups used in our sample code.
- Unlike in the sample code, in the real-life code you will not only validate the user against access permissions to pages but also other items such as media.
Note: You will learn more about the ExtranetFacade methods in the following chapter.
Now that you have created your custom extranet provider, go on to build and deploy it on your website.
Once you have built your solution, you are ready to deploy and use it on your website.
To deploy the solution, you can follow one of the two approaches:
- Automatic
- Manual
For automatic deployment, you should build an add-on for your extranet provider and then install it on your C1 CMS via its Packages system.
For manual deployment, you should copy the assembly you have just built to a specific folder on your website and plug it in via the C1 CMS configuration file.
For automatic deployment, you should build an add-on for your extranet provider following the standard add-on-building procedure.
In the Install.xsl file, you should specify the name of your custom provider, its class and its assembly.
The following is the sample for the SimpleExtranetProvider we have created:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()" /> </xsl:copy> </xsl:template> <xsl:template match="/configuration/ Composite.Community.Extranet.Plugins.ExtranetProviderConfiguration/ ExtranetProviderPlugins"> <xsl:copy> <xsl:apply-templates select="@* | node()" /> <xsl:if test="count(add[@name='Simple'])=0"> <add name="Simple" title="Simple Extranet" type="SimpleExtranetProvider.SimpleExtranetProvider, SimpleExtranetProvider" /> </xsl:if> </xsl:copy> </xsl:template> </xsl:stylesheet>
Listing 10: Sample Install.xsl for SimpleExtranetProvider
In the similar way, you should modify the Uninstall.xsl.
Once you have built the add-on, you can install it via the Packages system in the CMS Console (System > Packages > InstalledPackages > LocalPackages > Installlocalpackage).
To deploy the custom extranet provider manually:
- Copy the extranet provider assembly to the Bin subfolder in the root folder of your website.
- Open the C1 CMS configuration file found at \\<website>\App_Data\Composite\Composite.config
- Locate the <Composite.Community.Extranet.Plugins.ExtranetProviderConfiguration> section.
- Under the <ExtranetProviderPlugins> element add the name and title of your provider, its class and its assembly.
The following is the sample for the SimpleExtranetProvider we have created:
<add name="Simple" title="Simple Extranet" type="SimpleExtranetProvider.SimpleExtranetProvider, SimpleExtranetProvider" />
Listing 11: Sample of plugging in SimpleExtranetProvider
- Now restart the server and then refresh the browser window (or the tab) in which you have your CMS Console running.
Now that you have deployed the custom extranet provider, you can start using it.
Using Custom Extranet in C1 CMS
Once you have deployed your custom extranet provider, it will appear in the Extranet perspective as another extranet.
Figure 1: Custom extranet
This extranet presents the users retrieved from your database and assigned to specific groups in your custom extranet.
You can use this custom extranet to protect your website and media folders and manage extranet security on web pages and media files as you do with the default extranet provider.
Figure 2: Adding the custom extranet to a website