// Copyright © 2004, 2011, Oracle and/or its affiliates. All rights reserved. // // MySQL Connector/NET is licensed under the terms of the GPLv2 // , like most // MySQL Connectors. There are special exceptions to the terms and // conditions of the GPLv2 as it is applied to this software, see the // FLOSS License Exception // . // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published // by the Free Software Foundation; version 2 of the License. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // This code was contributed by Sean Wright (srwright@alcor.concordia.ca) on 2007-01-12 // The copyright was assigned and transferred under the terms of // the MySQL Contributor License Agreement (CLA) using System; using System.Web.Profile; using System.Configuration; using System.Collections.Specialized; using System.Web.Hosting; using MySql.Data.MySqlClient; using System.Configuration.Provider; using System.Collections; using System.Collections.Generic; using System.Text; using System.Data; using System.IO; using System.Globalization; using System.Web.Security; using MySql.Web.Common; using MySql.Web.Properties; using MySql.Web.General; namespace MySql.Web.Profile { /// /// /// public class MySQLProfileProvider : ProfileProvider { private string connectionString; private Application app; #region Abstract Members /// /// Initializes the provider. /// /// The friendly name of the provider. /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. /// The name of the provider is null. /// The name of the provider has a length of zero. /// An attempt is made to call on a provider after the provider has already been initialized. public override void Initialize(string name, NameValueCollection config) { if (config == null) throw new ArgumentNullException("config"); if (name == null || name.Length == 0) name = "MySQLProfileProvider"; if (string.IsNullOrEmpty(config["description"])) { config.Remove("description"); config.Add("description", "MySQL Profile provider"); } base.Initialize(name, config); try { string applicationName = GetConfigValue(config["applicationName"], HostingEnvironment.ApplicationVirtualPath); connectionString = ""; ConnectionStringSettings ConnectionStringSettings = ConfigurationManager.ConnectionStrings[ config["connectionStringName"]]; if (ConnectionStringSettings != null) connectionString = ConnectionStringSettings.ConnectionString.Trim(); if (String.IsNullOrEmpty(connectionString)) return; // make sure our schema is up to date SchemaManager.CheckSchema(connectionString, config); app = new Application(applicationName, base.Description); } catch (Exception ex) { throw new ProviderException(Resources.ErrorInitProfileProvider, ex); } } /// /// When overridden in a derived class, deletes all user-profile data /// for profiles in which the last activity date occurred before the /// specified date. /// /// One of the /// /// values, specifying whether anonymous, authenticated, or both /// types of profiles are deleted. /// A /// that identifies which user profiles are considered inactive. If the /// /// value of a user profile occurs on or before this date and time, the /// profile is considered inactive. /// /// The number of profiles deleted from the data source. /// public override int DeleteInactiveProfiles( ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) { using (MySqlConnection c = new MySqlConnection(connectionString)) { c.Open(); MySqlCommand queryCmd = new MySqlCommand( @"SELECT * FROM my_aspnet_users WHERE applicationId=@appId AND lastActivityDate < @lastActivityDate", c); queryCmd.Parameters.AddWithValue("@appId", app.FetchId(c)); queryCmd.Parameters.AddWithValue("@lastActivityDate", userInactiveSinceDate); if (authenticationOption == ProfileAuthenticationOption.Anonymous) queryCmd.CommandText += " AND isAnonymous = 1"; else if (authenticationOption == ProfileAuthenticationOption.Authenticated) queryCmd.CommandText += " AND isAnonymous = 0"; MySqlCommand deleteCmd = new MySqlCommand( "DELETE FROM my_aspnet_profiles WHERE userId = @userId", c); deleteCmd.Parameters.Add("@userId", MySqlDbType.UInt64); List uidList = new List(); using (MySqlDataReader reader = queryCmd.ExecuteReader()) { while (reader.Read()) uidList.Add(reader.GetUInt64("userId")); } int count = 0; foreach (ulong uid in uidList) { deleteCmd.Parameters[0].Value = uid; count += deleteCmd.ExecuteNonQuery(); } return count; } } /// /// When overridden in a derived class, deletes profile properties /// and information for profiles that match the supplied list of user names. /// /// A string array of user names for /// profiles to be deleted. /// /// The number of profiles deleted from the data source. /// public override int DeleteProfiles(string[] usernames) { using (MySqlConnection c = new MySqlConnection(connectionString)) { c.Open(); MySqlCommand queryCmd = new MySqlCommand( @"SELECT * FROM my_aspnet_users WHERE applicationId=@appId AND name = @name", c); queryCmd.Parameters.AddWithValue("@appId", app.FetchId(c)); queryCmd.Parameters.Add("@name", MySqlDbType.VarChar); MySqlCommand deleteCmd = new MySqlCommand( "DELETE FROM my_aspnet_profiles WHERE userId = @userId", c); deleteCmd.Parameters.Add("@userId", MySqlDbType.Int32); int count = 0; foreach (string name in usernames) { queryCmd.Parameters[1].Value = name; int uid = (int)queryCmd.ExecuteScalar(); deleteCmd.Parameters[0].Value = uid; count += deleteCmd.ExecuteNonQuery(); } return count; } } /// /// When overridden in a derived class, deletes profile properties /// and information for the supplied list of profiles. /// /// A /// of /// information about profiles that are to be deleted. /// /// The number of profiles deleted from the data source. /// public override int DeleteProfiles(ProfileInfoCollection profiles) { string[] s = new string[profiles.Count]; int i = 0; foreach (ProfileInfo p in profiles) s[i++] = p.UserName; return DeleteProfiles(s); } /// /// When overridden in a derived class, retrieves profile information /// for profiles in which the last activity date occurred on or before /// the specified date and the user name matches the specified user name. /// /// One of the /// values, /// specifying whether anonymous, authenticated, or both types of profiles /// are returned. /// The user name to search for. /// A /// that identifies which user profiles are considered inactive. If the /// value /// of a user profile occurs on or before this date and time, the profile /// is considered inactive. /// The index of the page of results to return. /// The size of the page of results to return. /// When this method returns, contains the total /// number of profiles. /// /// A containing /// user profile information for inactive profiles where the user name /// matches the supplied parameter. /// public override ProfileInfoCollection FindInactiveProfilesByUserName( ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) { return GetProfiles(authenticationOption, usernameToMatch, userInactiveSinceDate, pageIndex, pageSize, out totalRecords); } /// /// When overridden in a derived class, retrieves profile information /// for profiles in which the user name matches the specified user names. /// /// One of the /// values, /// specifying whether anonymous, authenticated, or both types of profiles /// are returned. /// The user name to search for. /// The index of the page of results to return. /// The size of the page of results to return. /// When this method returns, contains the total /// number of profiles. /// /// A containing /// user-profile information for profiles where the user name matches the /// supplied parameter. /// public override ProfileInfoCollection FindProfilesByUserName( ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { return GetProfiles(authenticationOption, usernameToMatch, DateTime.MinValue, pageIndex, pageSize, out totalRecords); } /// /// When overridden in a derived class, retrieves user-profile data /// from the data source for profiles in which the last activity date /// occurred on or before the specified date. /// /// One of the /// values, /// specifying whether anonymous, authenticated, or both types of profiles /// are returned. /// A /// that identifies which user profiles are considered inactive. If the /// of /// a user profile occurs on or before this date and time, the profile is /// considered inactive. /// The index of the page of results to return. /// The size of the page of results to return. /// When this method returns, contains the /// total number of profiles. /// /// A containing user-profile information about the inactive profiles. /// public override ProfileInfoCollection GetAllInactiveProfiles( ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) { return GetProfiles(authenticationOption, null, userInactiveSinceDate, pageIndex, pageSize, out totalRecords); } /// /// When overridden in a derived class, retrieves user profile data for /// all profiles in the data source. /// /// One of the /// values, /// specifying whether anonymous, authenticated, or both types of profiles /// are returned. /// The index of the page of results to return. /// The size of the page of results to return. /// When this method returns, contains the /// total number of profiles. /// /// A containing /// user-profile information for all profiles in the data source. /// public override ProfileInfoCollection GetAllProfiles( ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords) { return GetProfiles(authenticationOption, null, DateTime.MinValue, pageIndex, pageSize, out totalRecords); } /// /// When overridden in a derived class, returns the number of profiles /// in which the last activity date occurred on or before the specified /// date. /// /// One of the /// values, /// specifying whether anonymous, authenticated, or both types of profiles /// are returned. /// A /// that identifies which user profiles are considered inactive. If the /// of /// a user profile occurs on or before this date and time, the profile /// is considered inactive. /// /// The number of profiles in which the last activity date occurred on /// or before the specified date. /// public override int GetNumberOfInactiveProfiles( ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) { using (MySqlConnection c = new MySqlConnection(connectionString)) { c.Open(); MySqlCommand queryCmd = new MySqlCommand( @"SELECT COUNT(*) FROM my_aspnet_users WHERE applicationId = @appId AND lastActivityDate < @lastActivityDate", c); queryCmd.Parameters.AddWithValue("@appId", app.FetchId(c)); queryCmd.Parameters.AddWithValue("@lastActivityDate", userInactiveSinceDate); if (authenticationOption == ProfileAuthenticationOption.Anonymous) queryCmd.CommandText += " AND isAnonymous = 1"; else if (authenticationOption == ProfileAuthenticationOption.Authenticated) queryCmd.CommandText += " AND isAnonymous = 0"; return (int)queryCmd.ExecuteScalar(); } } /// /// Gets or sets the name of the currently running application. /// /// /// A that contains the application's shortened name, which does not contain a full path or extension, for example, SimpleAppSettings. public override string ApplicationName { get { return app.Name; } set { app.Name = value; } } /// /// Returns the collection of settings property values for the specified application instance and settings property group. /// /// A describing the current application use. /// A containing the settings property group whose values are to be retrieved. /// /// A containing the values for the specified settings property group. /// public override SettingsPropertyValueCollection GetPropertyValues( SettingsContext context, SettingsPropertyCollection collection) { SettingsPropertyValueCollection values = new SettingsPropertyValueCollection(); if (collection.Count < 1) return values; string username = (string)context["UserName"]; foreach (SettingsProperty property in collection) { if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(string)) property.SerializeAs = SettingsSerializeAs.String; else property.SerializeAs = SettingsSerializeAs.Xml; values.Add(new SettingsPropertyValue(property)); } if (String.IsNullOrEmpty(username)) return values; // retrieve encoded profile data from the database try { using (MySqlConnection c = new MySqlConnection(connectionString)) { c.Open(); MySqlCommand cmd = new MySqlCommand( @"SELECT * FROM my_aspnet_profiles p JOIN my_aspnet_users u ON u.id = p.userId WHERE u.applicationId = @appId AND u.name = @name", c); cmd.Parameters.AddWithValue("@appId", app.FetchId(c)); cmd.Parameters.AddWithValue("@name", username); MySqlDataAdapter da = new MySqlDataAdapter(cmd); DataTable dt = new DataTable(); da.Fill(dt); if (dt.Rows.Count > 0) DecodeProfileData(dt.Rows[0], values); return values; } } catch (Exception ex) { throw new ProviderException(Resources.UnableToRetrieveProfileData, ex); } } /// /// Sets the values of the specified group of property settings. /// /// A describing the current application usage. /// A representing the group of property settings to set. public override void SetPropertyValues( SettingsContext context, SettingsPropertyValueCollection collection) { bool isAuthenticated = (bool)context["IsAuthenticated"]; string username = (string)context["UserName"]; if (String.IsNullOrEmpty(username)) return; if (collection.Count < 1) return; string index = String.Empty; string stringData = String.Empty; byte[] binaryData = null; int count = EncodeProfileData(collection, isAuthenticated, ref index, ref stringData, ref binaryData); if (count < 1) return; MySqlTransaction txn = null; // save the encoded profile data to the database using (MySqlConnection connection = new MySqlConnection(connectionString)) { try { connection.Open(); txn = connection.BeginTransaction(); // either create a new user or fetch the existing user id long userId = SchemaManager.CreateOrFetchUserId(connection, username, app.EnsureId(connection), isAuthenticated); MySqlCommand cmd = new MySqlCommand( @"INSERT INTO my_aspnet_profiles VALUES (@userId, @index, @stringData, @binaryData, NULL) ON DUPLICATE KEY UPDATE valueindex=VALUES(valueindex), stringdata=VALUES(stringdata), binarydata=VALUES(binarydata)", connection); cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@userId", userId); cmd.Parameters.AddWithValue("@index", index); cmd.Parameters.AddWithValue("@stringData", stringData); cmd.Parameters.AddWithValue("@binaryData", binaryData); count = cmd.ExecuteNonQuery(); if (count == 0) throw new Exception(Resources.ProfileUpdateFailed); txn.Commit(); } catch (Exception ex) { if (txn != null) txn.Rollback(); throw new ProviderException(Resources.ProfileUpdateFailed, ex); } } } #endregion internal static void DeleteUserData(MySqlConnection connection, int userId) { MySqlCommand cmd = new MySqlCommand( "DELETE FROM my_aspnet_profiles WHERE userId=@userId", connection); cmd.Parameters.AddWithValue("@userId", userId); cmd.ExecuteNonQuery(); } #region Private Methods private void DecodeProfileData(DataRow profileRow, SettingsPropertyValueCollection values) { string indexData = (string)profileRow["valueindex"]; string stringData = (string)profileRow["stringData"]; byte[] binaryData = (byte[])profileRow["binaryData"]; if (indexData == null) return; string[] indexes = indexData.Split(':'); foreach (string index in indexes) { string[] parts = index.Split('/'); SettingsPropertyValue value = values[parts[0]]; if (value == null) continue; int pos = Int32.Parse(parts[2], CultureInfo.InvariantCulture); int len = Int32.Parse(parts[3], CultureInfo.InvariantCulture); if (len == -1) { value.PropertyValue = null; value.IsDirty = false; value.Deserialized = true; } else if (parts[1].Equals("0")) value.SerializedValue = stringData.Substring(pos, len); else { byte[] buf = new byte[len]; Buffer.BlockCopy(binaryData, pos, buf, 0, len); value.SerializedValue = buf; } } } private int EncodeProfileData(SettingsPropertyValueCollection collection, bool isAuthenticated, ref string index, ref string stringData, ref byte[] binaryData) { bool itemsToSave = false; // first we need to determine if there are any items that need saving // this is an optimization foreach (SettingsPropertyValue value in collection) { if (!value.IsDirty) continue; if (value.Property.Attributes["AllowAnonymous"].Equals(false) && !isAuthenticated) continue; itemsToSave = true; break; } if (!itemsToSave) return 0; StringBuilder indexBuilder = new StringBuilder(); StringBuilder stringDataBuilder = new StringBuilder(); MemoryStream binaryBuilder = new MemoryStream(); int count = 0; // ok, we have some values that need to be saved so we go back through foreach (SettingsPropertyValue value in collection) { // if the value has not been written to and is still using the default value // no need to save it if (value.UsingDefaultValue && !value.IsDirty) continue; // we don't save properties that require the user to be authenticated when the // current user is not authenticated. if (value.Property.Attributes["AllowAnonymous"].Equals(false) && !isAuthenticated) continue; count++; object propValue = value.SerializedValue; if ((value.Deserialized && value.PropertyValue == null) || value.SerializedValue == null) indexBuilder.AppendFormat("{0}//0/-1:", value.Name); else if (propValue is string) { indexBuilder.AppendFormat("{0}/0/{1}/{2}:", value.Name, stringDataBuilder.Length, (propValue as string).Length); stringDataBuilder.Append(propValue); } else { byte[] binaryValue = (byte[])propValue; indexBuilder.AppendFormat("{0}/1/{1}/{2}:", value.Name, binaryBuilder.Position, binaryValue.Length); binaryBuilder.Write(binaryValue, 0, binaryValue.Length); } } index = indexBuilder.ToString(); stringData = stringDataBuilder.ToString(); binaryData = binaryBuilder.ToArray(); return count; } private ProfileInfoCollection GetProfiles( ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) { List whereClauses = new List(); using (MySqlConnection c = new MySqlConnection(connectionString)) { c.Open(); MySqlCommand cmd = new MySqlCommand( @"SELECT p.*, u.name, u.isAnonymous, u.lastActivityDate, LENGTH(p.stringdata) + LENGTH(p.binarydata) AS profilesize FROM my_aspnet_profiles p JOIN my_aspnet_users u ON u.id = p.userId WHERE u.applicationId = @appId", c); cmd.Parameters.AddWithValue("@appId", app.FetchId(c)); if (usernameToMatch != null) { cmd.CommandText += " AND u.name LIKE @userName"; cmd.Parameters.AddWithValue("@userName", usernameToMatch); } if (userInactiveSinceDate != DateTime.MinValue) { cmd.CommandText += " AND u.lastActivityDate < @lastActivityDate"; cmd.Parameters.AddWithValue("@lastActivityDate", userInactiveSinceDate); } if (authenticationOption == ProfileAuthenticationOption.Anonymous) cmd.CommandText += " AND u.isAnonymous = 1"; else if (authenticationOption == ProfileAuthenticationOption.Authenticated) cmd.CommandText += " AND u.isAnonymous = 0"; cmd.CommandText += String.Format(" LIMIT {0},{1}", pageIndex * pageSize, pageSize); ProfileInfoCollection pic = new ProfileInfoCollection(); using (MySqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { ProfileInfo pi = new ProfileInfo( reader.GetString("name"), reader.GetBoolean("isAnonymous"), reader.GetDateTime("lastActivityDate"), reader.GetDateTime("lastUpdatedDate"), reader.GetInt32("profilesize")); pic.Add(pi); } } cmd.CommandText = "SELECT FOUND_ROWS()"; totalRecords = Convert.ToInt32(cmd.ExecuteScalar()); return pic; } } private static string GetConfigValue(string configValue, string defaultValue) { if (string.IsNullOrEmpty(configValue)) { return defaultValue; } return configValue; } #endregion } }