using System; using System.Collections.Generic; using System.Text; using ScrewTurn.Wiki.Plugins.SqlCommon; using ScrewTurn.Wiki.PluginFramework; using System.Data.SqlClient; namespace ScrewTurn.Wiki.Plugins.SqlServer { /// /// Implements a SQL Server-based users storage provider. /// public class SqlServerUsersStorageProvider : SqlUsersStorageProviderBase, IUsersStorageProviderV30 { private readonly ComponentInformation info = new ComponentInformation("SQL Server Users Storage Provider", "ScrewTurn Software", "3.0.0.341", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/SQLServerProv/Users.txt"); private readonly SqlServerCommandBuilder commandBuilder = new SqlServerCommandBuilder(); private const int CurrentSchemaVersion = 3000; /// /// Gets a new command with an open connection. /// /// The connection string. /// The command. private SqlCommand GetCommand(string connString) { return commandBuilder.GetCommand(connString, "select current_user", new List()) as SqlCommand; } /// /// Gets a new command builder object. /// /// The command builder. protected override ICommandBuilder GetCommandBuilder() { return commandBuilder; } /// /// Validates a connection string. /// /// The connection string to validate. /// If the connection string is invalid, the method throws . protected override void ValidateConnectionString(string connString) { SqlCommand cmd = null; try { cmd = GetCommand(connString); } catch(SqlException ex) { throw new InvalidConfigurationException("Provided connection string is not valid", ex); } catch(InvalidOperationException ex) { throw new InvalidConfigurationException("Provided connection string is not valid", ex); } catch(ArgumentException ex) { throw new InvalidConfigurationException("Provided connection string is not valid", ex); } finally { try { cmd.Connection.Close(); } catch { } } } /// /// Detects whether the database schema exists. /// /// true if the schema exists, false otherwise. private bool SchemaExists() { SqlCommand cmd = GetCommand(connString); cmd.CommandText = "select [Version] from [Version] where [Component] = 'Users'"; bool exists = false; try { int version = ExecuteScalar(cmd, -1); if(version > CurrentSchemaVersion) throw new InvalidConfigurationException("The version of the database schema is greater than the supported version"); exists = version != -1; } catch(SqlException) { exists = false; } finally { try { cmd.Connection.Close(); } catch { } } return exists; } /// /// Detects whether the database schema needs to be updated. /// /// true if an update is needed, false otherwise. private bool SchemaNeedsUpdate() { SqlCommand cmd = GetCommand(connString); cmd.CommandText = "select [Version] from [Version] where [Component] = 'Users'"; bool exists = false; try { int version = ExecuteScalar(cmd, -1); exists = version < CurrentSchemaVersion; } catch(SqlException) { exists = false; } finally { try { cmd.Connection.Close(); } catch { } } return exists; } /// /// Creates the standard database schema. /// private void CreateStandardSchema() { SqlCommand cmd = GetCommand(connString); cmd.CommandText = Properties.Resources.UsersDatabase; cmd.ExecuteNonQuery(); cmd.Connection.Close(); } /// /// Creates or updates the database schema if necessary. /// protected override void CreateOrUpdateDatabaseIfNecessary() { if(!SchemaExists()) { // Verify if an upgrade from version 2.0 is possible if(SchemaAllowsUpgradeFrom20()) { UpgradeFrom20(); } else { // If not, create the standard schema CreateStandardSchema(); } } if(SchemaNeedsUpdate()) { // Run minor update batches... } } /// /// Detects whether an upgrade is possible from version 2.0. /// /// true if the upgrade is possible, false otherwise. private bool SchemaAllowsUpgradeFrom20() { // Look for 'UsersProviderVersion' tables SqlCommand cmd = GetCommand(connString); cmd.CommandText = "select count(*) from sys.tables where [name] = 'UsersProviderVersion'"; int count = ExecuteScalar(cmd, -1); return count == 1; } /// /// Upgrades the database schema and data from version 2.0. /// private void UpgradeFrom20() { // 1. Load all user data in memory // 2. Rename old tables so they won't get in the way but the can still be recovered (_v2) // 3. Create new schema // 4. Add new users and default groups (admins, users) SqlCommand cmd = GetCommand(connString); cmd.CommandText = "select * from [User]"; SqlDataReader reader = cmd.ExecuteReader(); string administratorsGroup = host.GetSettingValue(SettingName.AdministratorsGroup); string usersGroup = host.GetSettingValue(SettingName.UsersGroup); List newUsers = new List(100); List passwordHashes = new List(100); while(reader.Read()) { string username = reader["Username"] as string; string passwordHash = reader["PasswordHash"] as string; string email = reader["Email"] as string; DateTime dateTime = (DateTime)reader["DateTime"]; bool active = (bool)reader["Active"]; bool admin = (bool)reader["Admin"]; UserInfo temp = new UserInfo(username, null, email, active, dateTime, this); temp.Groups = admin ? new string[] { administratorsGroup } : new string[] { usersGroup }; newUsers.Add(temp); passwordHashes.Add(passwordHash); } reader.Close(); cmd.Connection.Close(); cmd = GetCommand(connString); cmd.CommandText = "exec sp_rename 'UsersProviderVersion', 'UsersProviderVersion_v2'; exec sp_rename 'User', 'User_v2';"; cmd.ExecuteNonQuery(); cmd.Connection.Close(); CreateStandardSchema(); UserGroup admins = AddUserGroup(administratorsGroup, "Built-in Administrators"); UserGroup users = AddUserGroup(usersGroup, "Built-in Users"); for(int i = 0; i < newUsers.Count; i++) { cmd = GetCommand(connString); cmd.CommandText = "insert into [User] ([Username], [PasswordHash], [Email], [Active], [DateTime]) values (@Username, @PasswordHash, @Email, @Active, @DateTime)"; cmd.Parameters.Add(new SqlParameter("@Username", newUsers[i].Username)); cmd.Parameters.Add(new SqlParameter("@PasswordHash", passwordHashes[i])); cmd.Parameters.Add(new SqlParameter("@Email", newUsers[i].Email)); cmd.Parameters.Add(new SqlParameter("@Active", newUsers[i].Active)); cmd.Parameters.Add(new SqlParameter("@DateTime", newUsers[i].DateTime)); cmd.ExecuteNonQuery(); cmd.Connection.Close(); SetUserMembership(newUsers[i], newUsers[i].Groups); } host.UpgradeSecurityFlagsToGroupsAcl(admins, users); } /// /// Tries to load the configuration from a corresponding v2 provider. /// /// The configuration, or an empty string. protected override string TryLoadV2Configuration() { return host.GetProviderConfiguration("ScrewTurn.Wiki.PluginPack.SqlServerUsersStorageProvider"); } /// /// Tries to load the configuration of the corresponding settings storage provider. /// /// The configuration, or an empty string. protected override string TryLoadSettingsStorageProviderConfiguration() { return host.GetProviderConfiguration(typeof(SqlServerSettingsStorageProvider).FullName); } /// /// Gets the Information about the Provider. /// public override ComponentInformation Information { get { return info; } } /// /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. /// public override string ConfigHelpHtml { get { return "Connection string format:
Data Source=Database Address and Instance;Initial Catalog=Database name;User ID=login;Password=password;"; } } } }