diff --git a/ActiveDirectoryProvider/ActiveDirectoryProvider.cs b/ActiveDirectoryProvider/ActiveDirectoryProvider.cs
index 0a9fa9a..f32eca5 100644
--- a/ActiveDirectoryProvider/ActiveDirectoryProvider.cs
+++ b/ActiveDirectoryProvider/ActiveDirectoryProvider.cs
@@ -4,417 +4,931 @@ using System.DirectoryServices;
using System.Linq;
using System.Web;
using ScrewTurn.Wiki.PluginFramework;
+using System.Text;
+using System.DirectoryServices.ActiveDirectory;
namespace ScrewTurn.Wiki.Plugins.ActiveDirectory {
-
///
/// Implements a Users Storage Provider for Active Directory.
///
- public class ActiveDirectoryProvider {
-
- private const string HELP_HELP = "Configuration is set with the following parameters:
domain - The domain name (e.g. DOMAIN or DOMAIN.COM)
server - A domain controller to authenticate against (e.g. MYDC1)
admingroup - The AD group that will have admin access to the Wiki
usergroup The AD group that will have user access to the Wiki
username - An AD account username that has at minimum read access to the domain.
password - The password for the above username.
You will also need to adjust your web.config to set Authentication mode to Windows, and turn on NTLM on the server as well.";
- private IHostV30 m_Host;
- private string m_Server;
- private string m_Domain;
- private string m_SearchRoot;
- private string m_AdminGroup;
- private string m_UserGroup;
- private string m_Username;
- private string m_Password;
- private IUsersStorageProviderV30 m_StorageProvider;
-
- ///
- /// Not Implemented
- ///
- public bool TestAccount(UserInfo user, string password) {
- return false;
- }
-
- ///
- /// Not Implemented
- ///
- public UserInfo[] GetUsers() {
- return new UserInfo[] { };
- }
-
- ///
- /// Not Implemented
- ///
- public UserInfo AddUser(string username, string displayName, string password, string email, bool active, DateTime dateTime) {
- throw new NotImplementedException();
- }
-
- ///
- /// Not Implemented
- ///
- public UserInfo ModifyUser(UserInfo user, string newDisplayName, string newPassword, string newEmail, bool newActive) {
- throw new NotImplementedException();
- }
-
- ///
- /// Not Implemented
- ///
- public bool RemoveUser(UserInfo user) {
- throw new NotImplementedException();
- }
-
- ///
- /// Not Implemented
- ///
- public UserGroup[] GetUserGroups() {
- return new UserGroup[] { };
- }
-
- ///
- /// Not Implemented
- ///
- public UserGroup AddUserGroup(string name, string description) {
- throw new NotImplementedException();
- }
-
- ///
- /// Not Implemented
- ///
- public UserGroup ModifyUserGroup(UserGroup group, string description) {
- throw new NotImplementedException();
- }
-
- ///
- /// Not Implemented
- ///
- public bool RemoveUserGroup(UserGroup group) {
- throw new NotImplementedException();
- }
-
- ///
- /// Not Implemented
- ///
- public UserInfo SetUserMembership(UserInfo user, string[] groups) {
- throw new NotImplementedException();
- }
-
- ///
- /// Tries to log the user in manually
- ///
- public UserInfo TryManualLogin(string username, string password) {
-
- var info = m_StorageProvider.TryManualLogin(username, password);
-
- if (info != null)
- return info;
-
- try {
- using (var rootEntry = new DirectoryEntry(m_SearchRoot, username, password, AuthenticationTypes.Delegation | AuthenticationTypes.ReadonlyServer | AuthenticationTypes.Secure)) {
- var nativeObject = rootEntry.NativeObject;
- return GetUser(username);
- }
- } catch {
- return null;
- }
- }
-
- ///
- /// Tries to log the user in automagically
- ///
- public UserInfo TryAutoLogin(HttpContext context) {
-
- try {
-
- if (!context.User.Identity.IsAuthenticated)
- return null;
-
- var username = context.User.Identity.Name.Substring(context.User.Identity.Name.IndexOf("\\") + 1);
- return GetUser(username);
-
- } catch {
- return null;
- }
- }
-
- ///
- /// Gets the user info object for the currently logged in user.
- ///
- ///
- ///
- public UserInfo GetUser(string username) {
- var user = m_StorageProvider.GetUser(username);
- if (user != null)
- return user;
-
- using (var rootEntry = new DirectoryEntry(m_SearchRoot, m_Username, m_Password, AuthenticationTypes.Secure)) {
- using (var searcher = new DirectorySearcher(rootEntry) {
- Filter = string.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))", username)
- })
- {
- searcher.PropertiesToLoad.Add("objectClass");
- searcher.PropertiesToLoad.Add("member");
- searcher.PropertiesToLoad.Add("sAMAccountName");
- searcher.PropertiesToLoad.Add("sn");
- searcher.PropertiesToLoad.Add("givenName");
- searcher.PropertiesToLoad.Add("department");
- searcher.PropertiesToLoad.Add("title");
- searcher.PropertiesToLoad.Add("mail");
- searcher.PropertiesToLoad.Add("displayName");
-
- DirectoryEntry entry;
- try {
-
- entry = searcher.FindOne().GetDirectoryEntry();
-
- } catch {
-
- return null;
-
- }
-
- var displayName = string.Empty;
- var emailAddress = string.Empty;
- var adminGroup = false;
- var userGroup = false;
-
- var values = entry.Properties["displayName"];
- if (values != null && values.Count > 0)
- displayName = values[0].ToString();
-
- values = entry.Properties["mail"];
- if (values != null && values.Count > 0)
- emailAddress = values[0].ToString();
-
- var rootPath = entry.Path;
- rootPath = rootPath.Substring(0, rootPath.IndexOf("/", 7) + 1);
-
- for (var i = 0; i < entry.Properties["memberOf"].Count; i++) {
-
- using (var currentEntry = new DirectoryEntry(rootPath + entry.Properties["memberOf"][i])) {
-
- if (IsAccountTypeGroup(currentEntry)) {
-
- values = currentEntry.Properties["sAMAccountName"];
- if (values != null && values.Count > 0) {
-
- if (values[0].ToString() == m_AdminGroup)
- adminGroup = true;
-
- if (values[0].ToString() == m_UserGroup)
- userGroup = true;
-
- }
-
- }
-
- }
-
- }
-
-
- var userGroups = new List();
-
- if (adminGroup)
- userGroups.Add("Administrators");
- if (userGroup)
-
- userGroups.Add("Users");
-
- user = m_StorageProvider.AddUser(username, displayName, "jdzs98duadj2918j9sdjasd", emailAddress, true, DateTime.Now);
- user = m_StorageProvider.SetUserMembership(user, userGroups.ToArray());
-
- return user;
- }
- }
- }
-
- ///
- /// Gets the type of the account.
- ///
- private static bool IsAccountTypeGroup(DirectoryEntry entry) {
-
- var objectClass = entry.Properties["objectClass"];
-
- for (var i = 0; i < objectClass.Count; i++)
- if ((string)objectClass[i] == "group")
- return true;
-
- return false;
-
- }
-
- ///
- /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
- ///
- public UserInfo GetUserByEmail(string email) {
-
- return m_StorageProvider.GetUserByEmail(email);
-
- }
-
- ///
- /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
- ///
- public void NotifyCookieLogin(UserInfo user) {
-
- m_StorageProvider.NotifyCookieLogin(user);
-
- }
-
- ///
- /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
- ///
- public void NotifyLogout(UserInfo user) {
-
- m_StorageProvider.NotifyLogout(user);
-
- }
-
- ///
- /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
- ///
- public bool StoreUserData(UserInfo user, string key, string value) {
-
- return m_StorageProvider.StoreUserData(user, key, value);
-
- }
-
- ///
- /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
- ///
- public string RetrieveUserData(UserInfo user, string key) {
-
- return m_StorageProvider.RetrieveUserData(user, key);
-
- }
-
- ///
- /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
- ///
- public IDictionary RetrieveAllUserData(UserInfo user) {
-
- return m_StorageProvider.RetrieveAllUserData(user);
-
- }
-
- ///
- /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
- ///
- public IDictionary GetUsersWithData(string key) {
-
- return m_StorageProvider.GetUsersWithData(key);
-
- }
-
- ///
- /// True, always, we can't write back to AD
- ///
- public bool UserAccountsReadOnly {
- get { return true; }
- }
-
- ///
- /// No
- ///
- public bool UserGroupsReadOnly {
- get { return true; }
- }
-
- ///
- /// True, always, we can't write back to AD
- ///
- public bool GroupMembershipReadOnly {
- get { return true; }
- }
-
- ///
- /// True, always, we can't write back to AD
- ///
- public bool UsersDataReadOnly {
- get { return false; }
- }
-
- ///
- /// Inits the settings
- ///
- public void Init(IHostV30 host, string config) {
- m_Host = host;
- var provider = (from a in host.GetUsersStorageProviders(true)
- where a.Information.Name != this.Information.Name
- select a).FirstOrDefault();
-
- if (provider == null)
- throw new InvalidConfigurationException("This provider require an additional active storage provider for storing of active directory user information.");
-
- m_StorageProvider = provider;
- InitConfig(config);
- }
-
- ///
- /// Ignored
- ///
- public void Shutdown() {
- m_StorageProvider.Shutdown();
- }
-
- ///
- /// Plugin Information
- /// TODO return and complete
- ///
- public ComponentInformation Information {
- get { return new ComponentInformation("ActiveDirectoryProvider", "Threeplicate Srl", "3.0.1.417", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/ActiveDirectoryProvider.txt"); }
- }
-
- ///
- /// Plugin Help Text
- ///
- public string ConfigHelpHtml {
- get { return HELP_HELP; }
- }
-
- ///
- /// Configures the plugin based on the configuration settings
- ///
- ///
- private void InitConfig(string config)
- {
- if (string.IsNullOrEmpty(config))
- throw new InvalidConfigurationException("Config settings must be set before plugin can be used.");
-
- try {
- var configParts = config.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
- foreach (var configPart in configParts) {
- var setting = configPart.Substring(0, configPart.IndexOf("="));
- var value = configPart.Substring(configPart.IndexOf("=") + 1);
- switch (setting.ToLower().Trim())
- {
- case "domain":
- m_Domain = value;
- break;
- case "server":
- m_Server = value;
- break;
- case "admingroup":
- m_AdminGroup = value;
- break;
- case "usergroup":
- m_UserGroup = value;
- break;
- case "username":
- m_Username = value;
- break;
- case "password":
- m_Password = value;
- break;
- }
- }
-
- var ldap = string.Empty;
- if (!string.IsNullOrEmpty(m_Server))
- ldap = string.Format("{0}/", m_Server);
-
- var ldapParts = m_Domain.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
-
- m_SearchRoot = string.Format("LDAP://{0}DC={1}", ldap, string.Join(",DC=", ldapParts));
- } catch (Exception ex) {
- throw new InvalidConfigurationException("The configuration is invalid.", ex);
- }
- }
+ public class ActiveDirectoryProvider : IUsersStorageProviderV30 {
+ private const string HELP_HELP =
+ "Configuration Settings:
" +
+ "Map one or more domain groups to wiki groups (Users, Administrators, etc.):" +
+ "" +
+ "- GroupMap=somedomaingroup:somewikigroup[,anotherwikigroup[,...]]
" +
+ "
" +
+ "Give all users membership in common wiki groups (Users, etc.):" +
+ "" +
+ "- CommonGroups=somewikigroup[,anotherwikigroup[,...]]
" +
+ "
" +
+ "Give users with no wiki group membership default wiki groups (Users, etc.):" +
+ "" +
+ "- DefaultGroups=somewikigroup[,anotherwikigroup[,...]]
" +
+ "
" +
+ "Authenticate against a domain the web server is not joined to (optional, choose one):" +
+ "" +
+ "- Domain=some.domain
" +
+ "- Server=somedomaincontroller.some.domain
" +
+ "
" +
+ "Query active directory as a specific user on the domain (optional):" +
+ "" +
+ "- Username=someusername
" +
+ "- Password=somepassword
" +
+ "
" +
+ "Automatic login without a login form (optional):" +
+ "" +
+ "- Set Authentication mode to Windows in Web.config.
" +
+ "- Turn on Windows authentication on the web server.
" +
+ "
" +
+ "In case the user doesn't have an email in his ActiveDirectory profile, sets the email to a predefined value in the form name.surname@example.com (optional):" +
+ "" +
+ "- AutomaticMail=example.com
" +
+ "
" +
+ "Comments start with a semicolon \";\".";
+
+ private IHostV30 m_Host;
+ private IUsersStorageProviderV30 m_StorageProvider;
+ private Random m_Random;
+ private Config m_Config;
+
+ private class Config {
+ public string ServerName;
+ public string DomainName;
+ public string Username;
+ public string Password;
+ public string AutomaticMail;
+
+ private Dictionary GroupMap = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ ///
+ /// Gets the domain specified by the config or else the computer domain.
+ ///
+ /// The Domain object.
+ public Domain GetDomain() {
+ return GetDomain(Username, Password);
+ }
+
+ ///
+ /// Gets the domain specified by the config or else the computer domain using the given credentials (if any).
+ ///
+ /// The username.
+ /// The password.
+ /// The Domain object.
+ public Domain GetDomain(string username, string password) {
+ DirectoryContext context = null;
+
+ if(ServerName != null) {
+ context = new DirectoryContext(DirectoryContextType.DirectoryServer, ServerName, username, password);
+ }
+ else {
+ context = new DirectoryContext(DirectoryContextType.Domain, DomainName, username, password);
+ }
+
+ return Domain.GetDomain(context);
+ }
+
+ ///
+ /// Determine if the given user credentials are valid.
+ ///
+ /// The username.
+ /// The password.
+ /// true if credentials are valid otherwise false.
+ public bool ValidateCredentials(string username, string password) {
+ try {
+ using(Domain domain = GetDomain(username, password)) {
+ return true;
+ }
+ }
+
+ catch {
+ return false;
+ }
+ }
+
+ ///
+ /// Get the wiki groups for the given domain groups, if any.
+ ///
+ /// The domain groups.
+ /// The list of wiki groups.
+ public List GetWikiGroups(List domainGroups) {
+ // find all the wiki groups from the given domain groups
+ var wikiGroups = domainGroups.SelectMany(t => GetGroupMap(t)).ToList();
+
+ // add groups common to all users
+ wikiGroups.AddRange(CommonGroups);
+
+ // remove duplicates
+ wikiGroups = wikiGroups.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList();
+
+ // return the groups, if any
+ if(wikiGroups.Count > 0)
+ return wikiGroups;
+
+ // return the default groups, if any
+ return DefaultGroups.ToList();
+ }
+
+ ///
+ /// Get the wiki groups for the given domain group, if any.
+ ///
+ /// The domain group.
+ /// The array of string containing the wiki groups.
+ private string[] GetGroupMap(string domainGroup) {
+ string[] wikiGroups;
+
+ if(GroupMap.TryGetValue(domainGroup, out wikiGroups))
+ return wikiGroups;
+
+ return new string[0];
+ }
+
+ ///
+ /// Adds the given group map.
+ ///
+ /// The domain group.
+ /// The wiki groups.
+ public void AddGroupMap(string domainGroup, string[] wikiGroups) {
+ if(wikiGroups == null || wikiGroups.Length == 0)
+ GroupMap.Remove(domainGroup);
+ else
+ GroupMap[domainGroup] = wikiGroups;
+ }
+
+ ///
+ /// Returns true if the group map contains at least one entry.
+ ///
+ ///
+ /// true if this instance is group map set; otherwise, false.
+ ///
+ public bool IsGroupMapSet { get { return GroupMap.Count > 0; } }
+
+ ///
+ /// Groups that are common to all users, regardless of their domain group membership.
+ ///
+ /// The common groups.
+ private static readonly string CommonKey = String.Empty;
+ public string[] CommonGroups {
+ get { return GetGroupMap(CommonKey); }
+ set { AddGroupMap(CommonKey, value); }
+ }
+
+ ///
+ /// Groups that are used when a user has no other group membership.
+ ///
+ /// The default groups.
+ private static readonly string DefaultKey = ".DEFAULT!";
+ public string[] DefaultGroups {
+ get { return GetGroupMap(DefaultKey); }
+ set { AddGroupMap(DefaultKey, value); }
+ }
+ }
+
+ ///
+ /// Tests a Password for a User account.
+ ///
+ /// The User account.
+ /// The Password to test.
+ /// True if the Password is correct.
+ /// If or are null.
+ public bool TestAccount(UserInfo user, string password) {
+ return false;
+ }
+
+ ///
+ /// Gets the complete list of Users.
+ ///
+ /// All the Users, sorted by username.
+ public UserInfo[] GetUsers() {
+ return new UserInfo[] { };
+ }
+
+ ///
+ /// Adds a new User.
+ ///
+ /// The Username.
+ /// The display name (can be null).
+ /// The Password.
+ /// The Email address.
+ /// A value indicating whether the account is active.
+ /// The Account creation Date/Time.
+ ///
+ /// The correct object or null.
+ ///
+ /// If , or are null.
+ /// If , or are empty.
+ public UserInfo AddUser(string username, string displayName, string password, string email, bool active, DateTime dateTime) {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Modifies a User.
+ ///
+ /// The Username of the user to modify.
+ /// The new display name (can be null).
+ /// The new Password (null or blank to keep the current password).
+ /// The new Email address.
+ /// A value indicating whether the account is active.
+ ///
+ /// The correct object or null.
+ ///
+ /// If or are null.
+ /// If is empty.
+ public UserInfo ModifyUser(UserInfo user, string newDisplayName, string newPassword, string newEmail, bool newActive) {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Removes a User.
+ ///
+ /// The User to remove.
+ ///
+ /// True if the User has been removed successfully.
+ ///
+ /// If is null.
+ public bool RemoveUser(UserInfo user) {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Gets all the user groups.
+ ///
+ /// All the groups, sorted by name.
+ public UserGroup[] GetUserGroups() {
+ return new UserGroup[] { };
+ }
+
+ ///
+ /// Adds a new user group.
+ ///
+ /// The name of the group.
+ /// The description of the group.
+ ///
+ /// The correct object or null.
+ ///
+ /// If or are null.
+ /// If is empty.
+ public UserGroup AddUserGroup(string name, string description) {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Modifies a user group.
+ ///
+ /// The group to modify.
+ /// The new description of the group.
+ ///
+ /// The correct object or null.
+ ///
+ /// If or are null.
+ public UserGroup ModifyUserGroup(UserGroup group, string description) {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Removes a user group.
+ ///
+ /// The group to remove.
+ ///
+ /// true if the group is removed, false otherwise.
+ ///
+ /// If is null.
+ public bool RemoveUserGroup(UserGroup group) {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Sets the group memberships of a user account.
+ ///
+ /// The user account.
+ /// The groups the user account is member of.
+ ///
+ /// The correct object or null.
+ ///
+ /// If or are null.
+ public UserInfo SetUserMembership(UserInfo user, string[] groups) {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Tries to login a user directly through the provider.
+ ///
+ /// The username.
+ /// The password.
+ ///
+ /// The correct UserInfo object, or null.
+ ///
+ /// If or are null.
+ public UserInfo TryManualLogin(string username, string password) {
+ var info = StorageProvider.TryManualLogin(username, password);
+
+ if(info != null)
+ return info;
+
+ if(m_Config.ValidateCredentials(username, password))
+ return GetUser(username);
+
+ return null;
+ }
+
+ ///
+ /// Tries to login a user directly through the provider using
+ /// the current HttpContext and without username/password.
+ ///
+ /// The current HttpContext.
+ ///
+ /// The correct UserInfo object, or null.
+ ///
+ /// If is null.
+ public UserInfo TryAutoLogin(HttpContext context) {
+ try {
+ if(!context.User.Identity.IsAuthenticated)
+ return null;
+
+ var username = context.User.Identity.Name.Substring(context.User.Identity.Name.IndexOf(@"\") + 1);
+
+ return GetUser(username);
+ }
+
+ catch {
+ return null;
+ }
+ }
+
+ ///
+ /// Creates the primary group SID.
+ /// http://dunnry.com/blog/DeterminingYourPrimaryGroupInActiveDirectoryUsingNET.aspx
+ ///
+ /// The user sid.
+ /// The primary group ID.
+ private void CreatePrimaryGroupSID(byte[] userSid, int primaryGroupID) {
+ // convert the int into a byte array
+ byte[] rid = BitConverter.GetBytes(primaryGroupID);
+
+ // place the bytes into the user's SID byte array
+ // overwriting them as necessary
+ for(int i = 0; i < rid.Length; i++) {
+ userSid.SetValue(rid[i], new long[] { userSid.Length - (rid.Length - i) });
+ }
+ }
+
+ ///
+ /// Builds the octet string.
+ /// http://dunnry.com/blog/DeterminingYourPrimaryGroupInActiveDirectoryUsingNET.aspx
+ ///
+ /// The bytes.
+ ///
+ private string BuildOctetString(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+
+ for(int i = 0; i < bytes.Length; i++) {
+ sb.Append(bytes[i].ToString("X2"));
+ }
+
+ return sb.ToString();
+ }
+
+ private class UserProperties {
+ public byte[] ObjectSid;
+ public string Mail;
+ public string DisplayName;
+ public List MemberOf;
+ public int PrimaryGroupID;
+ }
+
+ ///
+ /// Gets the user info object for the currently logged in user.
+ ///
+ /// The username.
+ ///
+ /// The , or null.
+ ///
+ /// If is null.
+ /// If is empty.
+ public UserInfo GetUser(string username) {
+ try {
+ var user = StorageProvider.GetUser(username);
+
+ if(user != null)
+ return user;
+
+ // Active Directory Attributes
+ //
+ // http://msdn.microsoft.com/en-us/library/ms683980(VS.85).aspx
+ //
+ // Object-Sid -> objectSid (required, single-value)
+ // Object-Class -> objectClass (required, multi-value)
+ // Object-Category -> objectCategory (required, single-value)
+ // SAM-Account-Name -> sAMAccountName (required, single-value)
+ // E-mail-Addresses -> mail (optional, single-value)
+ // Display-Name -> displayName (optional, single-value)
+ // Is-Member-Of-DL -> memberOf (optional, multi-value)
+
+ using(Domain domain = m_Config.GetDomain()) {
+ SearchResult result;
+
+ try {
+ using(DirectoryEntry searchRoot = domain.GetDirectoryEntry()) {
+ using(var searcher = new DirectorySearcher(searchRoot)) {
+ searcher.Filter = String.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))", username);
+
+ searcher.PropertiesToLoad.Add("objectSid");
+ searcher.PropertiesToLoad.Add("mail");
+ searcher.PropertiesToLoad.Add("displayName");
+ searcher.PropertiesToLoad.Add("memberOf");
+ searcher.PropertiesToLoad.Add("primaryGroupID");
+
+ result = searcher.FindOne();
+ }
+ }
+
+ if(result == null)
+ return null;
+ }
+
+ catch(Exception ex) {
+ LogEntry(LogEntryType.Error, "Unable to complete search for user \"{0}\": {1}", username, ex);
+ return null;
+ }
+
+ UserProperties userProperties;
+
+ try {
+ userProperties = new UserProperties {
+ ObjectSid = result.Properties["objectSid"].Cast().Single(),
+ Mail = result.Properties["mail"].Cast().SingleOrDefault(),
+ DisplayName = result.Properties["displayName"].Cast().SingleOrDefault(),
+ MemberOf = result.Properties["memberOf"].Cast().ToList(),
+ PrimaryGroupID = result.Properties["primaryGroupID"].Cast().Single(),
+ };
+ }
+
+ catch(Exception ex) {
+ LogEntry(LogEntryType.Error, "Unable to access properties for user \"{0}\": {1}", username, ex);
+ return null;
+ }
+
+ if(userProperties.Mail == null) {
+ if(m_Config.AutomaticMail != null && !string.IsNullOrEmpty(userProperties.DisplayName)) {
+ userProperties.Mail = userProperties.DisplayName.Replace(" ", ".") + "@" + m_Config.AutomaticMail;
+ }
+ else {
+ LogEntry(LogEntryType.Error, "Cannot login user \"{0}\" because they have no email address.", username);
+ return null;
+ }
+ }
+
+ CreatePrimaryGroupSID(userProperties.ObjectSid, userProperties.PrimaryGroupID);
+
+ string primaryGroupPath = String.Format("", BuildOctetString(userProperties.ObjectSid));
+
+ userProperties.MemberOf.Add(primaryGroupPath);
+
+ var domainGroups = new List();
+
+ foreach(string memberOfPath in userProperties.MemberOf) {
+ try {
+ using(var memberOfEntry = domain.GetDirectoryEntry()) {
+ string basePath = memberOfEntry.Path.Remove(memberOfEntry.Path.LastIndexOf("/"));
+
+ memberOfEntry.Path = String.Format("{0}/{1}", basePath, memberOfPath);
+
+ var samAccountName = memberOfEntry.Properties["sAMAccountName"].Cast().Single();
+
+ domainGroups.Add(samAccountName);
+ }
+ }
+
+ catch(Exception ex) {
+ LogEntry(LogEntryType.Error, "Skipping group \"{0}\" due to lookup error: {1}", memberOfPath, ex);
+ continue;
+ }
+ }
+
+ var wikiGroups = m_Config.GetWikiGroups(domainGroups);
+
+ if(wikiGroups.Count == 0) {
+ LogEntry(LogEntryType.Error, "Refusing to create user \"{0}\" without any groups, please check the GroupMap configuration.", username);
+ return null;
+ }
+
+ user = StorageProvider.AddUser(username, userProperties.DisplayName, GeneratePassword(), userProperties.Mail, true, DateTime.Now);
+
+ if(user == null) {
+ LogEntry(LogEntryType.Error, "Failed to create user \"{0}\" using provider \"{1}\", but no error was given by the provider.", username, StorageProvider.GetType());
+ return null;
+ }
+
+ LogEntry(LogEntryType.General, "Created user \"{0}\" using provider \"{1}\", but no group membership has been set yet.", username, StorageProvider.GetType());
+
+ user = StorageProvider.SetUserMembership(user, wikiGroups.ToArray());
+
+ if(user == null) {
+ LogEntry(LogEntryType.Error, "Failed to set user membership for user \"{0}\" using provider \"{1}\", but no error was given by the provider.", username, StorageProvider.GetType());
+ return null;
+ }
+
+ LogEntry(LogEntryType.General, "Set user membership for user \"{0}\" using provider \"{1}\", user is ready for use.", username, StorageProvider.GetType());
+
+ return user;
+ }
+ }
+
+ catch(Exception ex) {
+ LogEntry(LogEntryType.Error, "Error looking up user: {0}", ex);
+ return null;
+ }
+ }
+
+
+ ///
+ /// Generate a random password of garbage since a non-zero length password is required
+ ///
+ /// The random password.
+ private string GeneratePassword() {
+ byte[] bytes = new byte[100];
+ m_Random.NextBytes(bytes);
+ return new String(bytes.Select(t => Convert.ToChar(t)).ToArray());
+ }
+
+
+ ///
+ /// Gets a user account.
+ /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
+ ///
+ /// The email address.
+ ///
+ /// The first user found with the specified email address, or null.
+ ///
+ /// If is null.
+ /// If is empty.
+ public UserInfo GetUserByEmail(string email) {
+ return StorageProvider.GetUserByEmail(email);
+
+ }
+
+
+ ///
+ /// Notifies the provider that a user has logged in through the authentication cookie.
+ /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
+ ///
+ /// The user who has logged in.
+ /// If is null.
+ public void NotifyCookieLogin(UserInfo user) {
+ StorageProvider.NotifyCookieLogin(user);
+ }
+
+
+ ///
+ /// Notifies the provider that a user has logged out.
+ /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
+ ///
+ /// The user who has logged out.
+ /// If is null.
+ public void NotifyLogout(UserInfo user) {
+ StorageProvider.NotifyLogout(user);
+ }
+
+
+ ///
+ /// Stores a user data element, overwriting the previous one if present.
+ /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
+ ///
+ /// The user the data belongs to.
+ /// The key of the data element (case insensitive).
+ /// The value of the data element, null for deleting the data.
+ ///
+ /// true if the data element is stored, false otherwise.
+ ///
+ /// If or are null.
+ /// If is empty.
+ public bool StoreUserData(UserInfo user, string key, string value) {
+ return StorageProvider.StoreUserData(user, key, value);
+ }
+
+
+ ///
+ /// Gets a user data element, if any.
+ /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
+ ///
+ /// The user the data belongs to.
+ /// The key of the data element.
+ ///
+ /// The value of the data element, or null if the element is not found.
+ ///
+ /// If or are null.
+ /// If is empty.
+ public string RetrieveUserData(UserInfo user, string key) {
+ return StorageProvider.RetrieveUserData(user, key);
+ }
+
+
+ ///
+ /// Retrieves all the user data elements for a user.
+ /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
+ ///
+ /// The user.
+ /// The user data elements (key->value).
+ /// If is null.
+ public IDictionary RetrieveAllUserData(UserInfo user) {
+ return StorageProvider.RetrieveAllUserData(user);
+ }
+
+
+ ///
+ /// Gets all the users that have the specified element in their data.
+ /// Not Implemented - Passed Directly to the IUsersStorageProviderV30
+ ///
+ /// The key of the data.
+ /// The users and the data.
+ /// If is null.
+ /// If is empty.
+ public IDictionary GetUsersWithData(string key) {
+ return StorageProvider.GetUsersWithData(key);
+ }
+
+
+ ///
+ /// Gets a value indicating whether user accounts are read-only.
+ ///
+ /// True, always, we can't write back to AD
+ public bool UserAccountsReadOnly {
+ get { return true; }
+ }
+
+
+ ///
+ /// Gets a value indicating whether user groups are read-only. If so, the provider
+ /// should support default user groups as defined in the wiki configuration.
+ ///
+ /// True, always, we can't write back to AD
+ public bool UserGroupsReadOnly {
+ get { return true; }
+ }
+
+ ///
+ /// Gets a value indicating whether group membership is read-only (if
+ /// is false, then this property must be false). If this property is true, the provider
+ /// should return membership data compatible with default user groups.
+ ///
+ /// True, always, we can't write back to AD
+ public bool GroupMembershipReadOnly {
+ get { return true; }
+ }
+
+
+ ///
+ /// Gets a value indicating whether users' data is read-only.
+ ///
+ /// True, always, we can't write back to AD
+ public bool UsersDataReadOnly {
+ get { return true; }
+ }
+
+ ///
+ /// Initializes the Storage Provider.
+ ///
+ /// The Host of the Component.
+ /// The Configuration data, if any.
+ /// If or are null.
+ /// If is not valid or is incorrect.
+ public void Init(IHostV30 host, string config) {
+ m_Host = host;
+ m_Random = new Random();
+
+ InitConfig(config);
+ }
+
+
+ ///
+ /// Returns the storage provider.
+ /// The storage provider is identified the first time it is needed, rather than at init.
+ /// This avoids a dependency on the storage provider being loaded first, which is not guaranteed
+ ///
+ /// The storage provider.
+ private IUsersStorageProviderV30 StorageProvider {
+ get {
+ if(m_StorageProvider == null) {
+ lock(m_Host) {
+ if(m_StorageProvider == null) {
+ m_StorageProvider = (from a in m_Host.GetUsersStorageProviders(true)
+ where a.Information.Name != this.Information.Name
+ select a).FirstOrDefault();
+
+ if(m_StorageProvider == null) {
+ LogEntry(LogEntryType.Error, "This provider requires an additional active storage provider for storing of active directory user information.");
+ throw new InvalidConfigurationException("This provider requires an additional active storage provider for storing of active directory user information.");
+ }
+ }
+ }
+ }
+
+ return m_StorageProvider;
+ }
+ }
+
+
+ ///
+ /// Method invoked on shutdown.
+ /// Ignored
+ ///
+ /// This method might not be invoked in some cases.
+ public void Shutdown() {
+ StorageProvider.Shutdown();
+ }
+
+
+ ///
+ /// Gets the Information about the Provider.
+ ///
+ /// The information
+ public ComponentInformation Information {
+ get { return new ComponentInformation("Active Directory Provider", "Threeplicate Srl", "3.0.2.509", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/ActiveDirectoryProvider.txt"); }
+ }
+
+
+ ///
+ /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed.
+ ///
+ ///
+ public string ConfigHelpHtml {
+ get { return HELP_HELP; }
+ }
+
+
+ ///
+ /// Logs a message from this plugin.
+ ///
+ /// Type of the entry.
+ /// The message.
+ /// The args.
+ private void LogEntry(LogEntryType entryType, string message, params object[] args) {
+ string entry = String.Format(message, args);
+ m_Host.LogEntry(entry, entryType, null, this);
+ }
+
+
+ ///
+ /// Configures the plugin based on the configuration settings.
+ ///
+ /// The config.
+ private void InitConfig(string config) {
+ Config newConfig = ParseConfig(config);
+
+ if(!newConfig.IsGroupMapSet) {
+ LogEntry(LogEntryType.Error, "No GroupMap entries found. Please make sure at least one valid GroupMap configuration entry exists.");
+ throw new InvalidConfigurationException("No GroupMap entries found. Please make sure at least one valid GroupMap configuration entry exists.");
+ }
+
+ if(newConfig.CommonGroups.Length > 0 && newConfig.DefaultGroups.Length > 0) {
+ LogEntry(LogEntryType.Warning, "DefaultGroups will be ignored because CommonGroups have been configured.");
+ newConfig.DefaultGroups = null;
+ }
+
+ if(newConfig.ServerName != null) {
+ if(newConfig.DomainName != null) {
+ LogEntry(LogEntryType.Warning,
+ "Domain and Server config keys are mutually exclusive, but both were given. " +
+ "The Domain entry will be ignored.");
+
+ newConfig.DomainName = null;
+ }
+
+ LogEntry(LogEntryType.General,
+ "Configured to use domain controller \"{0}\".",
+ newConfig.ServerName);
+ }
+ else {
+ if(newConfig.DomainName == null) {
+ try {
+ newConfig.DomainName = Domain.GetComputerDomain().Name;
+ }
+
+ catch(Exception ex) {
+ LogEntry(LogEntryType.Error, "Unable to auto-detect the computer domain: {0}", ex);
+ throw new InvalidConfigurationException("Unable to auto-detect the computer domain.", ex);
+ }
+
+ LogEntry(LogEntryType.General,
+ "Domain \"{0}\" was determined through auto-detection.",
+ newConfig.DomainName);
+ }
+ else {
+ LogEntry(LogEntryType.General,
+ "Configured to use domain \"{0}\".",
+ newConfig.DomainName);
+ }
+ }
+
+ try {
+ using(Domain domain = newConfig.GetDomain()) {
+ }
+ }
+
+ catch(Exception ex) {
+ LogEntry(LogEntryType.Error, "Unable to connect to active directory with configured username and password (if any): {0}", ex);
+ throw new InvalidConfigurationException("Unable to connect to active directory with configured username and password (if any).", ex);
+ }
+
+ m_Config = newConfig;
+ }
+
+
+ ///
+ /// Parses the plugin configuration string.
+ ///
+ /// The config.
+ /// A Config object representig the configuration string.
+ private Config ParseConfig(string config) {
+ Config newConfig = new Config();
+
+ try {
+ string[] configLines = config.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach(string configLine in configLines) {
+ string remainingConfigLine = configLine.Split(new[] { ';' }).First();
+
+ if(remainingConfigLine.Length == 0)
+ continue;
+
+ string[] configEntry = remainingConfigLine.Split(new[] { '=' }, 2);
+
+ if(configEntry.Length != 2) {
+ LogEntry(LogEntryType.Error,
+ "Config lines must be in the format \"Key=Value\". " +
+ "The config line \"{0}\" will be ignored.",
+ configLine);
+ continue;
+ }
+
+ string key = configEntry[0].Trim().ToLower();
+ string value = configEntry[1].Trim();
+
+ switch(key) {
+ case "server":
+ newConfig.ServerName = value;
+ break;
+
+ case "username":
+ newConfig.Username = value;
+ break;
+
+ case "password":
+ newConfig.Password = value;
+ break;
+
+ case "domain":
+ newConfig.DomainName = value;
+ break;
+
+ case "groupmap":
+ string[] groupMap = value.Split(new[] { ':' }, 2);
+
+ if(groupMap.Length != 2) {
+ LogEntry(LogEntryType.Error,
+ "GroupMap entries must be in the format \"GroupMap=DomainGroup:WikiGroup[,...]\". " +
+ "The config line \"{0}\" will be ignored.",
+ configLine);
+ break;
+ }
+
+ string fromDomainGroup = groupMap[0];
+ string[] toWikiGroups = groupMap[1].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Distinct(StringComparer.InvariantCultureIgnoreCase).ToArray();
+
+ newConfig.AddGroupMap(fromDomainGroup, toWikiGroups);
+
+ break;
+
+ case "commongroups":
+ string[] commonGroups = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Distinct(StringComparer.InvariantCultureIgnoreCase).ToArray();
+ newConfig.CommonGroups = commonGroups;
+ break;
+
+ case "defaultgroups":
+ string[] defaultGroups = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Distinct(StringComparer.InvariantCultureIgnoreCase).ToArray();
+ newConfig.DefaultGroups = defaultGroups;
+ break;
+
+ case "automaticmail":
+ newConfig.AutomaticMail = value;
+ break;
+
+ default:
+ LogEntry(LogEntryType.Error,
+ "Invalid config key, see help for valid options. " +
+ "The config line \"{0}\" will be ignored.",
+ configLine);
+ break;
+ }
+ }
+
+ return newConfig;
+ }
+
+ catch(Exception ex) {
+ LogEntry(LogEntryType.Error, "Error parsing the configuration: {0}", ex);
+ throw new InvalidConfigurationException("Error parsing the configuration.", ex);
+ }
+ }
}
-
}
diff --git a/ActiveDirectoryProvider/ActiveDirectoryProvider.csproj b/ActiveDirectoryProvider/ActiveDirectoryProvider.csproj
index 0437b8d..8ad289a 100644
--- a/ActiveDirectoryProvider/ActiveDirectoryProvider.csproj
+++ b/ActiveDirectoryProvider/ActiveDirectoryProvider.csproj
@@ -63,6 +63,9 @@
PluginFramework
+
+
+