From 99686770464291109629a62c8d0611dbe5b2ec0a Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Fri, 12 Apr 2019 23:08:01 +0200 Subject: [PATCH] Data Consent feature for DNN (#2683) * Add HasAgreedToTerms and HasAgreedToTermsOn to UserPortals and UserInfo. Add UserAgreedToTerms and ResetTermsAgreement methods to set these fields. * Introduce new portal settings for GDPR management * DB changes for GDPR * Adding logic to controller and add logging * Implement a fourth panel on the login screen to handle data consent * Provide the redirect in case of a 3rd party solution * Implementing a mechanism to anonymize users * Add new SPROCS to uninstall script * Add more fields to nullify when anonymizing a user * Replace GDPR with DataConsent in code to avoid confusion * Add user hard delete delay settings * Added new scheduled task to remove users that are marked for deletion in portals where the DataConsent mechanism is active with Delayed Hard Delete of users * Closing a couple of new methods from exposure to the public api * Add two other mechanisms for deletion: off and manual. Remove anonymization as it is too risky for now. * Moved sql to main 9.4.0 file upon request * RequestsRemoval instead of DesiresRemoval * Remove the logging of consent agreement --- DNN Platform/Library/Data/DataProvider.cs | 17 +- .../Library/DotNetNuke.Library.csproj | 1 + .../Entities/Portals/PortalSettings.cs | 1810 +++++++++-------- .../Portals/PortalSettingsController.cs | 17 + .../Library/Entities/Users/UserController.cs | 151 +- .../Library/Entities/Users/UserInfo.cs | 24 + .../Membership/AspNetMembershipProvider.cs | 237 ++- .../Security/Membership/MembershipProvider.cs | 3 + .../Security/Membership/UserValidStatus.cs | 3 +- .../Security/Profile/ProfileProvider.cs | 2 +- .../Library/Security/Roles/RoleController.cs | 16 + .../Services/Users/PurgeDeletedUsers.cs | 94 + .../App_LocalResources/Login.ascx.resx | 10 +- .../Admin/Authentication/Login.ascx | 4 + .../Admin/Authentication/Login.ascx.cs | 144 +- .../Authentication/Login.ascx.designer.cs | 18 + .../App_LocalResources/DataConsent.ascx.resx | 141 ++ .../Admin/Security/DataConsent.ascx | 32 + .../Admin/Security/DataConsent.ascx.cs | 162 ++ .../Security/DataConsent.ascx.designer.cs | 60 + Website/DotNetNuke.Website.csproj | 12 + .../SqlDataProvider/09.04.00.SqlDataProvider | 135 +- .../DotNetNuke.Data.SqlDataProvider | 3 + .../SqlDataProvider/UnInstall.SqlDataProvider | 6 + 24 files changed, 2045 insertions(+), 1057 deletions(-) create mode 100644 DNN Platform/Library/Services/Users/PurgeDeletedUsers.cs create mode 100644 Website/DesktopModules/Admin/Security/App_LocalResources/DataConsent.ascx.resx create mode 100644 Website/DesktopModules/Admin/Security/DataConsent.ascx create mode 100644 Website/DesktopModules/Admin/Security/DataConsent.ascx.cs create mode 100644 Website/DesktopModules/Admin/Security/DataConsent.ascx.designer.cs diff --git a/DNN Platform/Library/Data/DataProvider.cs b/DNN Platform/Library/Data/DataProvider.cs index 8b731ee0b9f..1a5072fe7fa 100644 --- a/DNN Platform/Library/Data/DataProvider.cs +++ b/DNN Platform/Library/Data/DataProvider.cs @@ -2384,6 +2384,11 @@ public virtual void RemoveUser(int userId, int portalId) ExecuteNonQuery("RemoveUser", userId, GetNull(portalId)); } + public virtual void ResetTermsAgreement(int portalId) + { + ExecuteNonQuery("ResetTermsAgreement", portalId); + } + public virtual void RestoreUser(int userId, int portalId) { ExecuteNonQuery("RestoreUser", userId, GetNull(portalId)); @@ -2407,7 +2412,7 @@ public virtual void UpdateUser(int userId, int portalID, string firstName, strin isApproved, refreshRoles, lastIpAddress, - passwordResetToken, + GetNull(passwordResetToken), GetNull(passwordResetExpiration), isDeleted, lastModifiedByUserID); @@ -2418,6 +2423,16 @@ public virtual void UpdateUserLastIpAddress(int userId, string lastIpAddress) ExecuteNonQuery("UpdateUserLastIpAddress", userId, lastIpAddress); } + public virtual void UserAgreedToTerms(int portalId, int userId) + { + ExecuteNonQuery("UserAgreedToTerms", portalId, userId); + } + + public virtual void UserRequestsRemoval(int portalId, int userId, bool remove) + { + ExecuteNonQuery("UserRequestsRemoval", portalId, userId, remove); + } + #endregion #region UserRole Methods diff --git a/DNN Platform/Library/DotNetNuke.Library.csproj b/DNN Platform/Library/DotNetNuke.Library.csproj index 8afcb206ffb..5a71de67daa 100644 --- a/DNN Platform/Library/DotNetNuke.Library.csproj +++ b/DNN Platform/Library/DotNetNuke.Library.csproj @@ -1493,6 +1493,7 @@ + ASPXCodeBehind diff --git a/DNN Platform/Library/Entities/Portals/PortalSettings.cs b/DNN Platform/Library/Entities/Portals/PortalSettings.cs index 8db797b8f24..75563cfe63a 100644 --- a/DNN Platform/Library/Entities/Portals/PortalSettings.cs +++ b/DNN Platform/Library/Entities/Portals/PortalSettings.cs @@ -1,561 +1,577 @@ -#region Copyright - -// -// DotNetNuke® - http://www.dotnetnuke.com -// Copyright (c) 2002-2018 -// by DotNetNuke Corporation -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -// to permit persons to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or substantial portions -// of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -#endregion - -#region Usings - -using System; -using System.Globalization; -using System.Linq; -using System.Web; -using DotNetNuke.Common.Utilities; -using DotNetNuke.Entities.Tabs; -using DotNetNuke.Entities.Users; -using DotNetNuke.Security; -using DotNetNuke.Services.Personalization; -using DotNetNuke.Services.Tokens; -using DotNetNuke.Common; - -#endregion - -namespace DotNetNuke.Entities.Portals -{ - /// ----------------------------------------------------------------------------- - /// - /// The PortalSettings class encapsulates all of the settings for the Portal, - /// as well as the configuration settings required to execute the current tab - /// view within the portal. - /// - /// ----------------------------------------------------------------------------- - [Serializable] - public partial class PortalSettings : BaseEntityInfo, IPropertyAccess - { - #region ControlPanelPermission enum - - public enum ControlPanelPermission - { - TabEditor, - ModuleEditor - } - - #endregion - - #region Mode enum - - public enum Mode - { - View, - Edit, - Layout - } - - #endregion - - #region PortalAliasMapping enum - - public enum PortalAliasMapping - { - None, - CanonicalUrl, - Redirect - } - - #endregion - - private TimeZoneInfo _timeZone = TimeZoneInfo.Local; - - #region Constructors - - public PortalSettings() - { - Registration = new RegistrationSettings(); - } - - public PortalSettings(int portalId) - : this(Null.NullInteger, portalId) - { - } - - public PortalSettings(int tabId, int portalId) - { - PortalId = portalId; - var portal = PortalController.Instance.GetPortal(portalId); - BuildPortalSettings(tabId, portal); - } - - /// ----------------------------------------------------------------------------- - /// - /// The PortalSettings Constructor encapsulates all of the logic - /// necessary to obtain configuration settings necessary to render - /// a Portal Tab view for a given request. - /// - /// - /// - /// The current tab - /// The current portal - /// ----------------------------------------------------------------------------- - public PortalSettings(int tabId, PortalAliasInfo portalAliasInfo) - { - PortalId = portalAliasInfo.PortalID; - PortalAlias = portalAliasInfo; - var portal = string.IsNullOrEmpty(portalAliasInfo.CultureCode) ? - PortalController.Instance.GetPortal(portalAliasInfo.PortalID) - : PortalController.Instance.GetPortal(portalAliasInfo.PortalID, portalAliasInfo.CultureCode); - - BuildPortalSettings(tabId, portal); - } - - public PortalSettings(PortalInfo portal) - : this(Null.NullInteger, portal) - { - } - - public PortalSettings(int tabId, PortalInfo portal) - { - PortalId = portal != null ? portal.PortalID : Null.NullInteger; - BuildPortalSettings(tabId, portal); - } - - private void BuildPortalSettings(int tabId, PortalInfo portal) - { - PortalSettingsController.Instance().LoadPortalSettings(this); - - if (portal == null) return; - - PortalSettingsController.Instance().LoadPortal(portal, this); - - var key = string.Join(":", "ActiveTab", portal.PortalID.ToString(), tabId.ToString()); - var items = HttpContext.Current != null ? HttpContext.Current.Items : null; - if (items != null && items.Contains(key)) - { - ActiveTab = items[key] as TabInfo; - } - else - { - ActiveTab = PortalSettingsController.Instance().GetActiveTab(tabId, this); - if (items != null && ActiveTab != null) - { - items[key] = ActiveTab; - } - } - } - - #endregion - - #region Auto-Properties - - public TabInfo ActiveTab { get; set; } - - public int AdministratorId { get; set; } - - public int AdministratorRoleId { get; set; } - - public string AdministratorRoleName { get; set; } - - public int AdminTabId { get; set; } - - public string BackgroundFile { get; set; } - - public int BannerAdvertising { get; set; } - - public string CultureCode { get; set; } - - public string Currency { get; set; } - - public string DefaultLanguage { get; set; } - - public string Description { get; set; } - - public string Email { get; set; } - - public DateTime ExpiryDate { get; set; } - - public string FooterText { get; set; } - - public Guid GUID { get; set; } - - public string HomeDirectory { get; set; } - - public string HomeSystemDirectory { get; set; } - - public int HomeTabId { get; set; } - - public float HostFee { get; set; } - - public int HostSpace { get; set; } - - public string KeyWords { get; set; } - - public int LoginTabId { get; set; } - - public string LogoFile { get; set; } - - public int PageQuota { get; set; } - - public int Pages { get; set; } - - public int PortalId { get; set; } - - public PortalAliasInfo PortalAlias { get; set; } - - public PortalAliasInfo PrimaryAlias { get; set; } - - public string PortalName { get; set; } - - public int RegisteredRoleId { get; set; } - - public string RegisteredRoleName { get; set; } - - public int RegisterTabId { get; set; } - - public RegistrationSettings Registration { get; set; } - - public int SearchTabId { get; set; } - - [Obsolete("Deprecated in 8.0.0. Scheduled removal in v11.0.0.")] - public int SiteLogHistory { get; set; } - - public int SplashTabId { get; set; } - - public int SuperTabId { get; set; } - - public int UserQuota { get; set; } - - public int UserRegistration { get; set; } - - public int Users { get; set; } - - public int UserTabId { get; set; } - - public int TermsTabId { get; set; } - - public int PrivacyTabId { get; set; } - - #endregion - - #region Read-Only Properties - - /// ----------------------------------------------------------------------------- - /// - /// Allows users to select their own UI culture. - /// When set to false (default) framework will allways same culture for both - /// CurrentCulture (content) and CurrentUICulture (interface) - /// - /// Defaults to False - /// ----------------------------------------------------------------------------- - public bool AllowUserUICulture { get; internal set; } - - public int CdfVersion { get; internal set; } - - public bool ContentLocalizationEnabled { get; internal set; } - - public ControlPanelPermission ControlPanelSecurity { get; internal set; } - - public string DefaultAdminContainer { get; internal set; } - - public string DefaultAdminSkin { get; internal set; } - - public string DefaultAuthProvider { get; internal set; } - - public Mode DefaultControlPanelMode { get; internal set; } - - public bool DefaultControlPanelVisibility { get; internal set; } - - public string DefaultIconLocation { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets the Default Module Id - /// - /// Defaults to Null.NullInteger - /// ----------------------------------------------------------------------------- - public int DefaultModuleId { get; internal set; } - - public string DefaultPortalContainer { get; internal set; } - - public string DefaultPortalSkin { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets the Default Tab Id - /// - /// Defaults to Null.NullInteger - /// ----------------------------------------------------------------------------- - public int DefaultTabId { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether Browser Language Detection is Enabled - /// - /// Defaults to True - /// ----------------------------------------------------------------------------- - public bool EnableBrowserLanguage { get; internal set; } - - public bool EnableCompositeFiles { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether to use the module effect in edit mode. - /// - /// Defaults to True - /// ----------------------------------------------------------------------------- - [Obsolete("Deprecated in Platform 7.4.0.. Scheduled removal in v11.0.0.")] - public bool EnableModuleEffect { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether to use the popup. - /// - /// Defaults to True - /// ----------------------------------------------------------------------------- - public bool EnablePopUps { get; internal set; } - - /// - /// Website Administrator whether receive the notification email when new user register. - /// - public bool EnableRegisterNotification { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether the Skin Widgets are enabled/supported - /// - /// Defaults to True - /// ----------------------------------------------------------------------------- - public bool EnableSkinWidgets { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether a cookie consent popup should be shown - /// - /// Defaults to False - /// ----------------------------------------------------------------------------- - public bool ShowCookieConsent { get; internal set; } - - /// - /// Link for the user to find out more about cookies. If not specified the link - /// shown will point to cookiesandyou.com - /// - public string CookieMoreLink { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether enable url language. - /// - /// Defaults to True - /// ----------------------------------------------------------------------------- - public bool EnableUrlLanguage { get; internal set; } - - public int ErrorPage404 { get; internal set; } - - public int ErrorPage500 { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether folders which are hidden or whose name begins with underscore - /// are included in folder synchronization. - /// - /// - /// Defaults to True - /// - /// ----------------------------------------------------------------------------- - public bool HideFoldersEnabled { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether hide the login link. - /// - /// Defaults to False. - /// ----------------------------------------------------------------------------- - public bool HideLoginControl { get; internal set; } - - public string HomeDirectoryMapPath { get; internal set; } - - public string HomeSystemDirectoryMapPath { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether the Inline Editor is enabled - /// - /// Defaults to True - /// ----------------------------------------------------------------------------- - public bool InlineEditorEnabled { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether to inlcude Common Words in the Search Index - /// - /// Defaults to False - /// ----------------------------------------------------------------------------- - public bool SearchIncludeCommon { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets whether to inlcude Numbers in the Search Index - /// - /// Defaults to False - /// ----------------------------------------------------------------------------- - public bool SearchIncludeNumeric { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets the filter used for inclusion of tag info - /// - /// - /// Defaults to "" - /// - /// ----------------------------------------------------------------------------- - public string SearchIncludedTagInfoFilter { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets the maximum Search Word length to index - /// - /// Defaults to 3 - /// ----------------------------------------------------------------------------- - public int SearchMaxWordlLength { get; internal set; } - - /// ----------------------------------------------------------------------------- - /// - /// Gets the minum Search Word length to index - /// - /// Defaults to 3 - /// ----------------------------------------------------------------------------- - public int SearchMinWordlLength { get; internal set; } - - public bool SSLEnabled { get; internal set; } - - public bool SSLEnforced { get; internal set; } - - public string SSLURL { get; internal set; } - - public string STDURL { get; internal set; } - - public int SMTPConnectionLimit { get; internal set; } - - public int SMTPMaxIdleTime { get; internal set; } - - #endregion - - #region Public Properties - - public CacheLevel Cacheability - { - get - { - return CacheLevel.fullyCacheable; - } - } - - public bool ControlPanelVisible - { - get - { - var setting = Convert.ToString(Personalization.GetProfile("Usability", "ControlPanelVisible" + PortalId)); - return String.IsNullOrEmpty(setting) ? DefaultControlPanelVisibility : Convert.ToBoolean(setting); - } - } - - public static PortalSettings Current - { - get - { - return PortalController.Instance.GetCurrentPortalSettings(); - } - } - - public string DefaultPortalAlias - { - get - { - foreach (var alias in PortalAliasController.Instance.GetPortalAliasesByPortalId(PortalId).Where(alias => alias.IsPrimary)) - { - return alias.HTTPAlias; - } - return String.Empty; - } - } - - public PortalAliasMapping PortalAliasMappingMode - { - get - { - return PortalSettingsController.Instance().GetPortalAliasMappingMode(PortalId); - } - } - - /// Gets the currently logged in user identifier. - /// The user identifier. - public int UserId - { - get - { - if (HttpContext.Current!= null && HttpContext.Current.Request.IsAuthenticated) - { - return UserInfo.UserID; - } - return Null.NullInteger; - } - } - - /// Gets the currently logged in user. - /// The current user information. - public UserInfo UserInfo - { - get - { - return UserController.Instance.GetCurrentUserInfo(); - } - } - - public Mode UserMode - { - get - { - Mode mode; - if (HttpContext.Current != null && HttpContext.Current.Request.IsAuthenticated) - { - mode = DefaultControlPanelMode; - string setting = Convert.ToString(Personalization.GetProfile("Usability", "UserMode" + PortalId)); - switch (setting.ToUpper()) - { - case "VIEW": - mode = Mode.View; - break; - case "EDIT": - mode = Mode.Edit; - break; - case "LAYOUT": - mode = Mode.Layout; - break; - } - } - else - { - mode = Mode.View; - } - return mode; - } - } - +#region Copyright + +// +// DotNetNuke® - http://www.dotnetnuke.com +// Copyright (c) 2002-2018 +// by DotNetNuke Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +// to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#endregion + +#region Usings + +using System; +using System.Globalization; +using System.Linq; +using System.Web; +using DotNetNuke.Common.Utilities; +using DotNetNuke.Entities.Tabs; +using DotNetNuke.Entities.Users; +using DotNetNuke.Security; +using DotNetNuke.Services.Personalization; +using DotNetNuke.Services.Tokens; +using DotNetNuke.Common; + +#endregion + +namespace DotNetNuke.Entities.Portals +{ + /// ----------------------------------------------------------------------------- + /// + /// The PortalSettings class encapsulates all of the settings for the Portal, + /// as well as the configuration settings required to execute the current tab + /// view within the portal. + /// + /// ----------------------------------------------------------------------------- + [Serializable] + public partial class PortalSettings : BaseEntityInfo, IPropertyAccess + { + #region ControlPanelPermission enum + + public enum ControlPanelPermission + { + TabEditor, + ModuleEditor + } + + #endregion + + #region Mode enum + + public enum Mode + { + View, + Edit, + Layout + } + + #endregion + + #region PortalAliasMapping enum + + public enum PortalAliasMapping + { + None, + CanonicalUrl, + Redirect + } + + #endregion + + #region Data Consent UserDeleteAction enum + public enum UserDeleteAction + { + Off = 0, + Manual = 1, + DelayedHardDelete = 2, + HardDelete = 3 + } + #endregion + + private TimeZoneInfo _timeZone = TimeZoneInfo.Local; + private bool _dataConsentActive = false; + private DateTime _dataConsentTermsLastChange = DateTime.MinValue; + private int _dataConsentConsentRedirect = -1; + private UserDeleteAction _dataConsentUserDeleteAction = UserDeleteAction.DelayedHardDelete; + private int _dataConsentDelay = 1; + private string _dataConsentDelayMeasurement = "d"; + + #region Constructors + + public PortalSettings() + { + Registration = new RegistrationSettings(); + } + + public PortalSettings(int portalId) + : this(Null.NullInteger, portalId) + { + } + + public PortalSettings(int tabId, int portalId) + { + PortalId = portalId; + var portal = PortalController.Instance.GetPortal(portalId); + BuildPortalSettings(tabId, portal); + } + + /// ----------------------------------------------------------------------------- + /// + /// The PortalSettings Constructor encapsulates all of the logic + /// necessary to obtain configuration settings necessary to render + /// a Portal Tab view for a given request. + /// + /// + /// + /// The current tab + /// The current portal + /// ----------------------------------------------------------------------------- + public PortalSettings(int tabId, PortalAliasInfo portalAliasInfo) + { + PortalId = portalAliasInfo.PortalID; + PortalAlias = portalAliasInfo; + var portal = string.IsNullOrEmpty(portalAliasInfo.CultureCode) ? + PortalController.Instance.GetPortal(portalAliasInfo.PortalID) + : PortalController.Instance.GetPortal(portalAliasInfo.PortalID, portalAliasInfo.CultureCode); + + BuildPortalSettings(tabId, portal); + } + + public PortalSettings(PortalInfo portal) + : this(Null.NullInteger, portal) + { + } + + public PortalSettings(int tabId, PortalInfo portal) + { + PortalId = portal != null ? portal.PortalID : Null.NullInteger; + BuildPortalSettings(tabId, portal); + } + + private void BuildPortalSettings(int tabId, PortalInfo portal) + { + PortalSettingsController.Instance().LoadPortalSettings(this); + + if (portal == null) return; + + PortalSettingsController.Instance().LoadPortal(portal, this); + + var key = string.Join(":", "ActiveTab", portal.PortalID.ToString(), tabId.ToString()); + var items = HttpContext.Current != null ? HttpContext.Current.Items : null; + if (items != null && items.Contains(key)) + { + ActiveTab = items[key] as TabInfo; + } + else + { + ActiveTab = PortalSettingsController.Instance().GetActiveTab(tabId, this); + if (items != null && ActiveTab != null) + { + items[key] = ActiveTab; + } + } + } + + #endregion + + #region Auto-Properties + + public TabInfo ActiveTab { get; set; } + + public int AdministratorId { get; set; } + + public int AdministratorRoleId { get; set; } + + public string AdministratorRoleName { get; set; } + + public int AdminTabId { get; set; } + + public string BackgroundFile { get; set; } + + public int BannerAdvertising { get; set; } + + public string CultureCode { get; set; } + + public string Currency { get; set; } + + public string DefaultLanguage { get; set; } + + public string Description { get; set; } + + public string Email { get; set; } + + public DateTime ExpiryDate { get; set; } + + public string FooterText { get; set; } + + public Guid GUID { get; set; } + + public string HomeDirectory { get; set; } + + public string HomeSystemDirectory { get; set; } + + public int HomeTabId { get; set; } + + public float HostFee { get; set; } + + public int HostSpace { get; set; } + + public string KeyWords { get; set; } + + public int LoginTabId { get; set; } + + public string LogoFile { get; set; } + + public int PageQuota { get; set; } + + public int Pages { get; set; } + + public int PortalId { get; set; } + + public PortalAliasInfo PortalAlias { get; set; } + + public PortalAliasInfo PrimaryAlias { get; set; } + + public string PortalName { get; set; } + + public int RegisteredRoleId { get; set; } + + public string RegisteredRoleName { get; set; } + + public int RegisterTabId { get; set; } + + public RegistrationSettings Registration { get; set; } + + public int SearchTabId { get; set; } + + [Obsolete("Deprecated in 8.0.0. Scheduled removal in v11.0.0.")] + public int SiteLogHistory { get; set; } + + public int SplashTabId { get; set; } + + public int SuperTabId { get; set; } + + public int UserQuota { get; set; } + + public int UserRegistration { get; set; } + + public int Users { get; set; } + + public int UserTabId { get; set; } + + public int TermsTabId { get; set; } + + public int PrivacyTabId { get; set; } + + #endregion + + #region Read-Only Properties + + /// ----------------------------------------------------------------------------- + /// + /// Allows users to select their own UI culture. + /// When set to false (default) framework will allways same culture for both + /// CurrentCulture (content) and CurrentUICulture (interface) + /// + /// Defaults to False + /// ----------------------------------------------------------------------------- + public bool AllowUserUICulture { get; internal set; } + + public int CdfVersion { get; internal set; } + + public bool ContentLocalizationEnabled { get; internal set; } + + public ControlPanelPermission ControlPanelSecurity { get; internal set; } + + public string DefaultAdminContainer { get; internal set; } + + public string DefaultAdminSkin { get; internal set; } + + public string DefaultAuthProvider { get; internal set; } + + public Mode DefaultControlPanelMode { get; internal set; } + + public bool DefaultControlPanelVisibility { get; internal set; } + + public string DefaultIconLocation { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets the Default Module Id + /// + /// Defaults to Null.NullInteger + /// ----------------------------------------------------------------------------- + public int DefaultModuleId { get; internal set; } + + public string DefaultPortalContainer { get; internal set; } + + public string DefaultPortalSkin { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets the Default Tab Id + /// + /// Defaults to Null.NullInteger + /// ----------------------------------------------------------------------------- + public int DefaultTabId { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether Browser Language Detection is Enabled + /// + /// Defaults to True + /// ----------------------------------------------------------------------------- + public bool EnableBrowserLanguage { get; internal set; } + + public bool EnableCompositeFiles { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether to use the module effect in edit mode. + /// + /// Defaults to True + /// ----------------------------------------------------------------------------- + [Obsolete("Deprecated in Platform 7.4.0.. Scheduled removal in v11.0.0.")] + public bool EnableModuleEffect { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether to use the popup. + /// + /// Defaults to True + /// ----------------------------------------------------------------------------- + public bool EnablePopUps { get; internal set; } + + /// + /// Website Administrator whether receive the notification email when new user register. + /// + public bool EnableRegisterNotification { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether the Skin Widgets are enabled/supported + /// + /// Defaults to True + /// ----------------------------------------------------------------------------- + public bool EnableSkinWidgets { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether a cookie consent popup should be shown + /// + /// Defaults to False + /// ----------------------------------------------------------------------------- + public bool ShowCookieConsent { get; internal set; } + + /// + /// Link for the user to find out more about cookies. If not specified the link + /// shown will point to cookiesandyou.com + /// + public string CookieMoreLink { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether enable url language. + /// + /// Defaults to True + /// ----------------------------------------------------------------------------- + public bool EnableUrlLanguage { get; internal set; } + + public int ErrorPage404 { get; internal set; } + + public int ErrorPage500 { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether folders which are hidden or whose name begins with underscore + /// are included in folder synchronization. + /// + /// + /// Defaults to True + /// + /// ----------------------------------------------------------------------------- + public bool HideFoldersEnabled { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether hide the login link. + /// + /// Defaults to False. + /// ----------------------------------------------------------------------------- + public bool HideLoginControl { get; internal set; } + + public string HomeDirectoryMapPath { get; internal set; } + + public string HomeSystemDirectoryMapPath { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether the Inline Editor is enabled + /// + /// Defaults to True + /// ----------------------------------------------------------------------------- + public bool InlineEditorEnabled { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether to inlcude Common Words in the Search Index + /// + /// Defaults to False + /// ----------------------------------------------------------------------------- + public bool SearchIncludeCommon { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets whether to inlcude Numbers in the Search Index + /// + /// Defaults to False + /// ----------------------------------------------------------------------------- + public bool SearchIncludeNumeric { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets the filter used for inclusion of tag info + /// + /// + /// Defaults to "" + /// + /// ----------------------------------------------------------------------------- + public string SearchIncludedTagInfoFilter { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets the maximum Search Word length to index + /// + /// Defaults to 3 + /// ----------------------------------------------------------------------------- + public int SearchMaxWordlLength { get; internal set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets the minum Search Word length to index + /// + /// Defaults to 3 + /// ----------------------------------------------------------------------------- + public int SearchMinWordlLength { get; internal set; } + + public bool SSLEnabled { get; internal set; } + + public bool SSLEnforced { get; internal set; } + + public string SSLURL { get; internal set; } + + public string STDURL { get; internal set; } + + public int SMTPConnectionLimit { get; internal set; } + + public int SMTPMaxIdleTime { get; internal set; } + + #endregion + + #region Public Properties + + public CacheLevel Cacheability + { + get + { + return CacheLevel.fullyCacheable; + } + } + + public bool ControlPanelVisible + { + get + { + var setting = Convert.ToString(Personalization.GetProfile("Usability", "ControlPanelVisible" + PortalId)); + return String.IsNullOrEmpty(setting) ? DefaultControlPanelVisibility : Convert.ToBoolean(setting); + } + } + + public static PortalSettings Current + { + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } + } + + public string DefaultPortalAlias + { + get + { + foreach (var alias in PortalAliasController.Instance.GetPortalAliasesByPortalId(PortalId).Where(alias => alias.IsPrimary)) + { + return alias.HTTPAlias; + } + return String.Empty; + } + } + + public PortalAliasMapping PortalAliasMappingMode + { + get + { + return PortalSettingsController.Instance().GetPortalAliasMappingMode(PortalId); + } + } + + /// Gets the currently logged in user identifier. + /// The user identifier. + public int UserId + { + get + { + if (HttpContext.Current != null && HttpContext.Current.Request.IsAuthenticated) + { + return UserInfo.UserID; + } + return Null.NullInteger; + } + } + + /// Gets the currently logged in user. + /// The current user information. + public UserInfo UserInfo + { + get + { + return UserController.Instance.GetCurrentUserInfo(); + } + } + + public Mode UserMode + { + get + { + Mode mode; + if (HttpContext.Current != null && HttpContext.Current.Request.IsAuthenticated) + { + mode = DefaultControlPanelMode; + string setting = Convert.ToString(Personalization.GetProfile("Usability", "UserMode" + PortalId)); + switch (setting.ToUpper()) + { + case "VIEW": + mode = Mode.View; + break; + case "EDIT": + mode = Mode.Edit; + break; + case "LAYOUT": + mode = Mode.Layout; + break; + } + } + else + { + mode = Mode.View; + } + return mode; + } + } + /// /// Get a value indicating whether the current portal is in maintenance mode (if either this specific portal or the entire instance is locked). If locked, any actions which update the database should be disabled. /// @@ -572,312 +588,372 @@ public bool IsThisPortalLocked get { return PortalController.GetPortalSettingAsBoolean("IsLocked", PortalId, false); } } - public TimeZoneInfo TimeZone - { - get { return _timeZone; } - set - { - _timeZone = value; - PortalController.UpdatePortalSetting(PortalId, "TimeZone", value.Id, true); - } - } - - - public string PageHeadText - { - get - { - // For New Install - string pageHead = ""; - string setting; - if (PortalController.Instance.GetPortalSettings(PortalId).TryGetValue("PageHeadText", out setting)) - { - // Hack to store empty string portalsetting with non empty default value - pageHead = (setting == "false") ? "" : setting; - } - return pageHead; - } - } - - /* - * add on the top of the module - * - * Desactivate this remove the html5 compatibility warnings - * (and make the output smaller) - * - */ - public bool InjectModuleHyperLink - { - get - { - return PortalController.GetPortalSettingAsBoolean("InjectModuleHyperLink", PortalId, true); - } - } - /* - * generates a : Page.Response.AddHeader("X-UA-Compatible", ""); - * - - */ - public string AddCompatibleHttpHeader - { - get - { - string CompatibleHttpHeader = "IE=edge"; - string setting; - if (PortalController.Instance.GetPortalSettings(PortalId).TryGetValue("AddCompatibleHttpHeader", out setting)) - { - // Hack to store empty string portalsetting with non empty default value - CompatibleHttpHeader = (setting == "false") ? "" : setting; - } - return CompatibleHttpHeader; - } - } - - /* - * add a cachebuster parameter to generated file URI's - * - * of the form ver=[file timestame] ie ver=2015-02-17-162255-735 - * - */ - public bool AddCachebusterToResourceUris - { - get - { - return PortalController.GetPortalSettingAsBoolean("AddCachebusterToResourceUris", PortalId, true); - } - } - - /// - /// If this is true, then regular users can't send message to specific user/group. - /// - public bool DisablePrivateMessage - { - get - { - return PortalController.GetPortalSetting("DisablePrivateMessage", PortalId, "N") == "Y"; - } - } - - #endregion - - #region IPropertyAccess Members - - public string GetProperty(string propertyName, string format, CultureInfo formatProvider, UserInfo accessingUser, Scope accessLevel, ref bool propertyNotFound) - { - var outputFormat = string.Empty; - if (format == string.Empty) - { - outputFormat = "g"; - } - var lowerPropertyName = propertyName.ToLowerInvariant(); - if (accessLevel == Scope.NoSettings) - { - propertyNotFound = true; - return PropertyAccess.ContentLocked; - } - propertyNotFound = true; - var result = string.Empty; - var isPublic = true; - switch (lowerPropertyName) - { - case "url": - propertyNotFound = false; - result = PropertyAccess.FormatString(PortalAlias.HTTPAlias, format); - break; - case "fullurl": //return portal alias with protocol - propertyNotFound = false; - result = PropertyAccess.FormatString(Globals.AddHTTP(PortalAlias.HTTPAlias), format); - break; - case "passwordreminderurl": //if regsiter page defined in portal settings, then get that page url, otherwise return home page. - propertyNotFound = false; - var reminderUrl = Globals.AddHTTP(PortalAlias.HTTPAlias); - if (RegisterTabId > Null.NullInteger) - { - reminderUrl = Globals.RegisterURL(string.Empty, string.Empty); - } - result = PropertyAccess.FormatString(reminderUrl, format); - break; - case "portalid": - propertyNotFound = false; - result = (PortalId.ToString(outputFormat, formatProvider)); - break; - case "portalname": - propertyNotFound = false; - result = PropertyAccess.FormatString(PortalName, format); - break; - case "homedirectory": - propertyNotFound = false; - result = PropertyAccess.FormatString(HomeDirectory, format); - break; - case "homedirectorymappath": - isPublic = false; - propertyNotFound = false; - result = PropertyAccess.FormatString(HomeDirectoryMapPath, format); - break; - case "logofile": - propertyNotFound = false; - result = PropertyAccess.FormatString(LogoFile, format); - break; - case "footertext": - propertyNotFound = false; - var footerText = FooterText.Replace("[year]", DateTime.Now.Year.ToString()); - result = PropertyAccess.FormatString(footerText, format); - break; - case "expirydate": - isPublic = false; - propertyNotFound = false; - result = (ExpiryDate.ToString(outputFormat, formatProvider)); - break; - case "userregistration": - isPublic = false; - propertyNotFound = false; - result = (UserRegistration.ToString(outputFormat, formatProvider)); - break; - case "banneradvertising": - isPublic = false; - propertyNotFound = false; - result = (BannerAdvertising.ToString(outputFormat, formatProvider)); - break; - case "currency": - propertyNotFound = false; - result = PropertyAccess.FormatString(Currency, format); - break; - case "administratorid": - isPublic = false; - propertyNotFound = false; - result = (AdministratorId.ToString(outputFormat, formatProvider)); - break; - case "email": - propertyNotFound = false; - result = PropertyAccess.FormatString(Email, format); - break; - case "hostfee": - isPublic = false; - propertyNotFound = false; - result = (HostFee.ToString(outputFormat, formatProvider)); - break; - case "hostspace": - isPublic = false; - propertyNotFound = false; - result = (HostSpace.ToString(outputFormat, formatProvider)); - break; - case "pagequota": - isPublic = false; - propertyNotFound = false; - result = (PageQuota.ToString(outputFormat, formatProvider)); - break; - case "userquota": - isPublic = false; - propertyNotFound = false; - result = (UserQuota.ToString(outputFormat, formatProvider)); - break; - case "administratorroleid": - isPublic = false; - propertyNotFound = false; - result = (AdministratorRoleId.ToString(outputFormat, formatProvider)); - break; - case "administratorrolename": - isPublic = false; - propertyNotFound = false; - result = PropertyAccess.FormatString(AdministratorRoleName, format); - break; - case "registeredroleid": - isPublic = false; - propertyNotFound = false; - result = (RegisteredRoleId.ToString(outputFormat, formatProvider)); - break; - case "registeredrolename": - isPublic = false; - propertyNotFound = false; - result = PropertyAccess.FormatString(RegisteredRoleName, format); - break; - case "description": - propertyNotFound = false; - result = PropertyAccess.FormatString(Description, format); - break; - case "keywords": - propertyNotFound = false; - result = PropertyAccess.FormatString(KeyWords, format); - break; - case "backgroundfile": - propertyNotFound = false; - result = PropertyAccess.FormatString(BackgroundFile, format); - break; - case "admintabid": - isPublic = false; - propertyNotFound = false; - result = AdminTabId.ToString(outputFormat, formatProvider); - break; - case "supertabid": - isPublic = false; - propertyNotFound = false; - result = SuperTabId.ToString(outputFormat, formatProvider); - break; - case "splashtabid": - isPublic = false; - propertyNotFound = false; - result = SplashTabId.ToString(outputFormat, formatProvider); - break; - case "hometabid": - isPublic = false; - propertyNotFound = false; - result = HomeTabId.ToString(outputFormat, formatProvider); - break; - case "logintabid": - isPublic = false; - propertyNotFound = false; - result = LoginTabId.ToString(outputFormat, formatProvider); - break; - case "registertabid": - isPublic = false; - propertyNotFound = false; - result = RegisterTabId.ToString(outputFormat, formatProvider); - break; - case "usertabid": - isPublic = false; - propertyNotFound = false; - result = UserTabId.ToString(outputFormat, formatProvider); - break; - case "defaultlanguage": - propertyNotFound = false; - result = PropertyAccess.FormatString(DefaultLanguage, format); - break; - case "users": - isPublic = false; - propertyNotFound = false; - result = Users.ToString(outputFormat, formatProvider); - break; - case "pages": - isPublic = false; - propertyNotFound = false; - result = Pages.ToString(outputFormat, formatProvider); - break; - case "contentvisible": - isPublic = false; - break; - case "controlpanelvisible": - isPublic = false; - propertyNotFound = false; - result = PropertyAccess.Boolean2LocalizedYesNo(ControlPanelVisible, formatProvider); - break; - } - if (!isPublic && accessLevel != Scope.Debug) - { - propertyNotFound = true; - result = PropertyAccess.ContentLocked; - } - return result; - } - - #endregion - - #region Public Methods - - public PortalSettings Clone() - { - return (PortalSettings)MemberwiseClone(); - } - - #endregion - } -} + public TimeZoneInfo TimeZone + { + get { return _timeZone; } + set + { + _timeZone = value; + PortalController.UpdatePortalSetting(PortalId, "TimeZone", value.Id, true); + } + } + + + public string PageHeadText + { + get + { + // For New Install + string pageHead = ""; + string setting; + if (PortalController.Instance.GetPortalSettings(PortalId).TryGetValue("PageHeadText", out setting)) + { + // Hack to store empty string portalsetting with non empty default value + pageHead = (setting == "false") ? "" : setting; + } + return pageHead; + } + } + + /* + * add on the top of the module + * + * Desactivate this remove the html5 compatibility warnings + * (and make the output smaller) + * + */ + public bool InjectModuleHyperLink + { + get + { + return PortalController.GetPortalSettingAsBoolean("InjectModuleHyperLink", PortalId, true); + } + } + /* + * generates a : Page.Response.AddHeader("X-UA-Compatible", ""); + * + + */ + public string AddCompatibleHttpHeader + { + get + { + string CompatibleHttpHeader = "IE=edge"; + string setting; + if (PortalController.Instance.GetPortalSettings(PortalId).TryGetValue("AddCompatibleHttpHeader", out setting)) + { + // Hack to store empty string portalsetting with non empty default value + CompatibleHttpHeader = (setting == "false") ? "" : setting; + } + return CompatibleHttpHeader; + } + } + + /* + * add a cachebuster parameter to generated file URI's + * + * of the form ver=[file timestame] ie ver=2015-02-17-162255-735 + * + */ + public bool AddCachebusterToResourceUris + { + get + { + return PortalController.GetPortalSettingAsBoolean("AddCachebusterToResourceUris", PortalId, true); + } + } + + /// + /// If this is true, then regular users can't send message to specific user/group. + /// + public bool DisablePrivateMessage + { + get + { + return PortalController.GetPortalSetting("DisablePrivateMessage", PortalId, "N") == "Y"; + } + } + + public bool DataConsentActive + { + get { return _dataConsentActive; } + set + { + _dataConsentActive = value; + PortalController.UpdatePortalSetting(PortalId, "DataConsentActive", value.ToString(), true); + } + } + + public DateTime DataConsentTermsLastChange + { + get { return _dataConsentTermsLastChange; } + set + { + _dataConsentTermsLastChange = value; + PortalController.UpdatePortalSetting(PortalId, "DataConsentTermsLastChange", value.ToString("u"), true); + } + } + + public int DataConsentConsentRedirect + { + get { return _dataConsentConsentRedirect; } + set + { + _dataConsentConsentRedirect = value; + PortalController.UpdatePortalSetting(PortalId, "DataConsentConsentRedirect", value.ToString(), true); + } + } + + public UserDeleteAction DataConsentUserDeleteAction + { + get { return _dataConsentUserDeleteAction; } + set + { + _dataConsentUserDeleteAction = value; + PortalController.UpdatePortalSetting(PortalId, "DataConsentUserDeleteAction", ((int)value).ToString(), true); + } + } + + public int DataConsentDelay + { + get { return _dataConsentDelay; } + set + { + _dataConsentDelay = value; + PortalController.UpdatePortalSetting(PortalId, "DataConsentDelay", value.ToString(), true); + } + } + + public string DataConsentDelayMeasurement + { + get { return _dataConsentDelayMeasurement; } + set + { + _dataConsentDelayMeasurement = value; + PortalController.UpdatePortalSetting(PortalId, "DataConsentDelayMeasurement", value, true); + } + } + + #endregion + + #region IPropertyAccess Members + + public string GetProperty(string propertyName, string format, CultureInfo formatProvider, UserInfo accessingUser, Scope accessLevel, ref bool propertyNotFound) + { + var outputFormat = string.Empty; + if (format == string.Empty) + { + outputFormat = "g"; + } + var lowerPropertyName = propertyName.ToLowerInvariant(); + if (accessLevel == Scope.NoSettings) + { + propertyNotFound = true; + return PropertyAccess.ContentLocked; + } + propertyNotFound = true; + var result = string.Empty; + var isPublic = true; + switch (lowerPropertyName) + { + case "url": + propertyNotFound = false; + result = PropertyAccess.FormatString(PortalAlias.HTTPAlias, format); + break; + case "fullurl": //return portal alias with protocol + propertyNotFound = false; + result = PropertyAccess.FormatString(Globals.AddHTTP(PortalAlias.HTTPAlias), format); + break; + case "passwordreminderurl": //if regsiter page defined in portal settings, then get that page url, otherwise return home page. + propertyNotFound = false; + var reminderUrl = Globals.AddHTTP(PortalAlias.HTTPAlias); + if (RegisterTabId > Null.NullInteger) + { + reminderUrl = Globals.RegisterURL(string.Empty, string.Empty); + } + result = PropertyAccess.FormatString(reminderUrl, format); + break; + case "portalid": + propertyNotFound = false; + result = (PortalId.ToString(outputFormat, formatProvider)); + break; + case "portalname": + propertyNotFound = false; + result = PropertyAccess.FormatString(PortalName, format); + break; + case "homedirectory": + propertyNotFound = false; + result = PropertyAccess.FormatString(HomeDirectory, format); + break; + case "homedirectorymappath": + isPublic = false; + propertyNotFound = false; + result = PropertyAccess.FormatString(HomeDirectoryMapPath, format); + break; + case "logofile": + propertyNotFound = false; + result = PropertyAccess.FormatString(LogoFile, format); + break; + case "footertext": + propertyNotFound = false; + var footerText = FooterText.Replace("[year]", DateTime.Now.Year.ToString()); + result = PropertyAccess.FormatString(footerText, format); + break; + case "expirydate": + isPublic = false; + propertyNotFound = false; + result = (ExpiryDate.ToString(outputFormat, formatProvider)); + break; + case "userregistration": + isPublic = false; + propertyNotFound = false; + result = (UserRegistration.ToString(outputFormat, formatProvider)); + break; + case "banneradvertising": + isPublic = false; + propertyNotFound = false; + result = (BannerAdvertising.ToString(outputFormat, formatProvider)); + break; + case "currency": + propertyNotFound = false; + result = PropertyAccess.FormatString(Currency, format); + break; + case "administratorid": + isPublic = false; + propertyNotFound = false; + result = (AdministratorId.ToString(outputFormat, formatProvider)); + break; + case "email": + propertyNotFound = false; + result = PropertyAccess.FormatString(Email, format); + break; + case "hostfee": + isPublic = false; + propertyNotFound = false; + result = (HostFee.ToString(outputFormat, formatProvider)); + break; + case "hostspace": + isPublic = false; + propertyNotFound = false; + result = (HostSpace.ToString(outputFormat, formatProvider)); + break; + case "pagequota": + isPublic = false; + propertyNotFound = false; + result = (PageQuota.ToString(outputFormat, formatProvider)); + break; + case "userquota": + isPublic = false; + propertyNotFound = false; + result = (UserQuota.ToString(outputFormat, formatProvider)); + break; + case "administratorroleid": + isPublic = false; + propertyNotFound = false; + result = (AdministratorRoleId.ToString(outputFormat, formatProvider)); + break; + case "administratorrolename": + isPublic = false; + propertyNotFound = false; + result = PropertyAccess.FormatString(AdministratorRoleName, format); + break; + case "registeredroleid": + isPublic = false; + propertyNotFound = false; + result = (RegisteredRoleId.ToString(outputFormat, formatProvider)); + break; + case "registeredrolename": + isPublic = false; + propertyNotFound = false; + result = PropertyAccess.FormatString(RegisteredRoleName, format); + break; + case "description": + propertyNotFound = false; + result = PropertyAccess.FormatString(Description, format); + break; + case "keywords": + propertyNotFound = false; + result = PropertyAccess.FormatString(KeyWords, format); + break; + case "backgroundfile": + propertyNotFound = false; + result = PropertyAccess.FormatString(BackgroundFile, format); + break; + case "admintabid": + isPublic = false; + propertyNotFound = false; + result = AdminTabId.ToString(outputFormat, formatProvider); + break; + case "supertabid": + isPublic = false; + propertyNotFound = false; + result = SuperTabId.ToString(outputFormat, formatProvider); + break; + case "splashtabid": + isPublic = false; + propertyNotFound = false; + result = SplashTabId.ToString(outputFormat, formatProvider); + break; + case "hometabid": + isPublic = false; + propertyNotFound = false; + result = HomeTabId.ToString(outputFormat, formatProvider); + break; + case "logintabid": + isPublic = false; + propertyNotFound = false; + result = LoginTabId.ToString(outputFormat, formatProvider); + break; + case "registertabid": + isPublic = false; + propertyNotFound = false; + result = RegisterTabId.ToString(outputFormat, formatProvider); + break; + case "usertabid": + isPublic = false; + propertyNotFound = false; + result = UserTabId.ToString(outputFormat, formatProvider); + break; + case "defaultlanguage": + propertyNotFound = false; + result = PropertyAccess.FormatString(DefaultLanguage, format); + break; + case "users": + isPublic = false; + propertyNotFound = false; + result = Users.ToString(outputFormat, formatProvider); + break; + case "pages": + isPublic = false; + propertyNotFound = false; + result = Pages.ToString(outputFormat, formatProvider); + break; + case "contentvisible": + isPublic = false; + break; + case "controlpanelvisible": + isPublic = false; + propertyNotFound = false; + result = PropertyAccess.Boolean2LocalizedYesNo(ControlPanelVisible, formatProvider); + break; + } + if (!isPublic && accessLevel != Scope.Debug) + { + propertyNotFound = true; + result = PropertyAccess.ContentLocked; + } + return result; + } + + #endregion + + #region Public Methods + + public PortalSettings Clone() + { + return (PortalSettings)MemberwiseClone(); + } + + #endregion + } +} diff --git a/DNN Platform/Library/Entities/Portals/PortalSettingsController.cs b/DNN Platform/Library/Entities/Portals/PortalSettingsController.cs index 8a23f2eb9da..05e7a94d462 100644 --- a/DNN Platform/Library/Entities/Portals/PortalSettingsController.cs +++ b/DNN Platform/Library/Entities/Portals/PortalSettingsController.cs @@ -306,6 +306,23 @@ public virtual void LoadPortalSettings(PortalSettings portalSettings) if (timeZone != null) portalSettings.TimeZone = timeZone; } + + setting = settings.GetValueOrDefault("DataConsentActive", "False"); + portalSettings.DataConsentActive = bool.Parse(setting); + setting = settings.GetValueOrDefault("DataConsentTermsLastChange", ""); + if (!string.IsNullOrEmpty(setting)) + { + portalSettings.DataConsentTermsLastChange = DateTime.Parse(setting); + } + setting = settings.GetValueOrDefault("DataConsentConsentRedirect", "-1"); + portalSettings.DataConsentConsentRedirect = int.Parse(setting); + setting = settings.GetValueOrDefault("DataConsentUserDeleteAction", "0"); + portalSettings.DataConsentUserDeleteAction = (PortalSettings.UserDeleteAction)int.Parse(setting); + setting = settings.GetValueOrDefault("DataConsentDelay", "1"); + portalSettings.DataConsentDelay = int.Parse(setting); + setting = settings.GetValueOrDefault("DataConsentDelayMeasurement", "d"); + portalSettings.DataConsentDelayMeasurement = setting; + } protected virtual void UpdateSkinSettings(TabInfo activeTab, PortalSettings portalSettings) diff --git a/DNN Platform/Library/Entities/Users/UserController.cs b/DNN Platform/Library/Entities/Users/UserController.cs index 7cb9e3b19fd..d48306b4be1 100644 --- a/DNN Platform/Library/Entities/Users/UserController.cs +++ b/DNN Platform/Library/Entities/Users/UserController.cs @@ -19,14 +19,6 @@ // DEALINGS IN THE SOFTWARE. #endregion -using System; -using System.Collections; -using System.Collections.Generic; -using System.Configuration; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Web; using DotNetNuke.Collections.Internal; using DotNetNuke.Common; using DotNetNuke.Common.Utilities; @@ -47,6 +39,14 @@ using DotNetNuke.Services.Log.EventLog; using DotNetNuke.Services.Mail; using DotNetNuke.Services.Messaging.Data; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Web; using MembershipProvider = DotNetNuke.Security.Membership.MembershipProvider; namespace DotNetNuke.Entities.Users @@ -242,8 +242,8 @@ private static SharedDictionary GetUserLookupDictionary(int portalI { var masterPortalId = GetEffectivePortalId(portalId); var cacheKey = string.Format(DataCache.UserLookupCacheKey, masterPortalId); - return CBO.GetCachedObject>(new CacheItemArgs(cacheKey, DataCache.UserLookupCacheTimeOut, - DataCache.UserLookupCachePriority), (c) => new SharedDictionary(),true); + return CBO.GetCachedObject>(new CacheItemArgs(cacheKey, DataCache.UserLookupCacheTimeOut, + DataCache.UserLookupCachePriority), (c) => new SharedDictionary(), true); } internal static Hashtable GetUserSettings(int portalId, Hashtable settings) @@ -702,6 +702,37 @@ public static void ApproveUser(UserInfo user) AutoAssignUsersToRoles(user, settings.PortalId); } + /// + /// User has agreed to terms and conditions. The time is recorded at the same time in SQL. + /// + /// The user that agreed. + public static void UserAgreedToTerms(UserInfo user) + { + Requires.NotNull("user", user); + MembershipProvider.Instance().UserAgreedToTerms(user); + } + + /// + /// When called all users in the portal will need to agree to terms and conditions again. + /// + /// The portal for which to reset. + public static void ResetTermsAgreement(int portalId) + { + Requires.NotNull("portalId", portalId); + MembershipProvider.Instance().ResetTermsAgreement(portalId); + } + + /// + /// A user may request that their account be removed. This sets a flag on the user portal + /// so further processing may occur manually by the site admins + /// + /// The user that desires to be removed. + public static void UserRequestsRemoval(UserInfo user, bool remove) + { + Requires.NotNull("user", user); + MembershipProvider.Instance().UserRequestsRemoval(user, remove); + } + /// ----------------------------------------------------------------------------- /// /// ChangePassword attempts to change the users password @@ -1059,7 +1090,7 @@ public static bool DeleteUser(ref UserInfo user, bool notify, bool deleteAdmin) //send email notification to portal administrator that the user was removed from the portal SendDeleteEmailNotifications(user, portalSettings); } - + DataCache.ClearPortalUserCountCache(user.PortalID); DataCache.ClearUserCache(user.PortalID, user.Username); @@ -1720,6 +1751,11 @@ public static void MoveUserToPortal(UserInfo user, PortalInfo portal, bool merge RemoveUser(user); } + /// + /// Permanently deletes all users marked as deleted from a portal. It will delete the membership + /// user as well if the user has no other portals + /// + /// Portal ID to get the deleted users for public static void RemoveDeletedUsers(int portalId) { var arrUsers = GetDeletedUsers(portalId); @@ -1733,6 +1769,13 @@ public static void RemoveDeletedUsers(int portalId) } } + /// + /// Permanently delete a user and the associated user folder on disk. + /// This also deletes the membership user if the user is + /// not a member of any other portal. + /// + /// The user to delete + /// public static bool RemoveUser(UserInfo user) { int portalId = user.PortalID; @@ -1750,37 +1793,7 @@ public static bool RemoveUser(UserInfo user) EventLogController.Instance.AddLog("Username", user.Username, portalSettings, user.UserID, EventLogController.EventLogType.USER_REMOVED); //Delete userFolder - DNN-3787 - var userFolderPath = ((PathUtils)PathUtils.Instance).GetUserFolderPathInternal(user); - var folderPortalId = user.IsSuperUser ? Null.NullInteger : user.PortalID; - var userFolder = FolderManager.Instance.GetFolder(folderPortalId, userFolderPath); - if (userFolder != null) - { - FolderManager.Instance.Synchronize(folderPortalId, userFolderPath, true, true); - var notDeletedSubfolders = new List(); - FolderManager.Instance.DeleteFolder(userFolder, notDeletedSubfolders); - - if (notDeletedSubfolders.Count == 0) - { - //try to remove the parent folder if there is no other users use this folder. - var parentFolder = FolderManager.Instance.GetFolder(userFolder.ParentID); - FolderManager.Instance.Synchronize(folderPortalId, parentFolder.FolderPath, true, true); - if (parentFolder != null && !FolderManager.Instance.GetFolders(parentFolder).Any()) - { - FolderManager.Instance.DeleteFolder(parentFolder, notDeletedSubfolders); - - if (notDeletedSubfolders.Count == 0) - { - //try to remove the root folder if there is no other users use this folder. - var rootFolder = FolderManager.Instance.GetFolder(parentFolder.ParentID); - FolderManager.Instance.Synchronize(folderPortalId, rootFolder.FolderPath, true, true); - if (rootFolder != null && !FolderManager.Instance.GetFolders(rootFolder).Any()) - { - FolderManager.Instance.DeleteFolder(rootFolder, notDeletedSubfolders); - } - } - } - } - } + DeleteUserFolder(user); DataCache.ClearPortalCache(portalId, false); DataCache.ClearUserCache(portalId, user.Username); @@ -1794,6 +1807,46 @@ public static bool RemoveUser(UserInfo user) return retValue; } + /// + /// Delete the contents and folder that belongs to a user in a specific portal + /// + /// The user for whom to delete the folder. + /// Note the PortalID is taken to specify which portal to delete the folder from. + private static void DeleteUserFolder(UserInfo user) + { + var userFolderPath = ((PathUtils)PathUtils.Instance).GetUserFolderPathInternal(user); + var folderPortalId = user.IsSuperUser ? Null.NullInteger : user.PortalID; + var userFolder = FolderManager.Instance.GetFolder(folderPortalId, userFolderPath); + if (userFolder != null) + { + FolderManager.Instance.Synchronize(folderPortalId, userFolderPath, true, true); + var notDeletedSubfolders = new List(); + FolderManager.Instance.DeleteFolder(userFolder, notDeletedSubfolders); + + if (notDeletedSubfolders.Count == 0) + { + //try to remove the parent folder if there is no other users use this folder. + var parentFolder = FolderManager.Instance.GetFolder(userFolder.ParentID); + FolderManager.Instance.Synchronize(folderPortalId, parentFolder.FolderPath, true, true); + if (parentFolder != null && !FolderManager.Instance.GetFolders(parentFolder).Any()) + { + FolderManager.Instance.DeleteFolder(parentFolder, notDeletedSubfolders); + + if (notDeletedSubfolders.Count == 0) + { + //try to remove the root folder if there is no other users use this folder. + var rootFolder = FolderManager.Instance.GetFolder(parentFolder.ParentID); + FolderManager.Instance.Synchronize(folderPortalId, rootFolder.FolderPath, true, true); + if (rootFolder != null && !FolderManager.Instance.GetFolders(rootFolder).Any()) + { + FolderManager.Instance.DeleteFolder(rootFolder, notDeletedSubfolders); + } + } + } + } + } + } + /// /// reset and change password /// used by admin/host users who do not need to supply an "old" password @@ -2029,6 +2082,7 @@ internal static void UpdateUser(int portalId, UserInfo user, bool loggedAction, } if (!user.Membership.Approving) return; + user.Membership.ConfirmApproved(); EventManager.Instance.OnUserApproved(new UserEventArgs { User = user }); } @@ -2224,6 +2278,18 @@ public static UserValidStatus ValidateUser(UserInfo objUser, int portalId, bool } } + // Check if user needs to consent to terms + if (validStatus == UserValidStatus.VALID && !(objUser.IsSuperUser || PortalSettings.Current.AdministratorId == objUser.UserID)) + { + if (PortalSettings.Current.DataConsentActive) + { + if (!objUser.HasAgreedToTerms) + { + validStatus = UserValidStatus.MUSTAGREETOTERMS; + } + } + } + //Check if Profile needs updating if (validStatus == UserValidStatus.VALID) { @@ -2233,6 +2299,7 @@ public static UserValidStatus ValidateUser(UserInfo objUser, int portalId, bool validStatus = UserValidStatus.UPDATEPROFILE; } } + return validStatus; } diff --git a/DNN Platform/Library/Entities/Users/UserInfo.cs b/DNN Platform/Library/Entities/Users/UserInfo.cs index e8863642615..f52dea4a259 100644 --- a/DNN Platform/Library/Entities/Users/UserInfo.cs +++ b/DNN Platform/Library/Entities/Users/UserInfo.cs @@ -194,6 +194,30 @@ public UserMembership Membership [Browsable(false)] public int PortalID { get; set; } + /// ----------------------------------------------------------------------------- + /// + /// Gets and sets whether the user has agreed to the terms and conditions + /// + /// ----------------------------------------------------------------------------- + [Browsable(false)] + public bool HasAgreedToTerms { get; set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets and sets when the user last agreed to the terms and conditions + /// + /// ----------------------------------------------------------------------------- + [Browsable(false)] + public DateTime HasAgreedToTermsOn { get; set; } + + /// ----------------------------------------------------------------------------- + /// + /// Gets and sets whether the user has requested they be removed from the site + /// + /// ----------------------------------------------------------------------------- + [Browsable(false)] + public bool RequestsRemoval { get; set; } + /// ----------------------------------------------------------------------------- /// /// Gets and sets the Profile Object diff --git a/DNN Platform/Library/Security/Membership/AspNetMembershipProvider.cs b/DNN Platform/Library/Security/Membership/AspNetMembershipProvider.cs index 670bb3e310f..09fefc4e826 100644 --- a/DNN Platform/Library/Security/Membership/AspNetMembershipProvider.cs +++ b/DNN Platform/Library/Security/Membership/AspNetMembershipProvider.cs @@ -23,16 +23,6 @@ #region Usings -using System; -using System.Collections; -using System.Collections.Generic; -using System.Configuration.Provider; -using System.Data; -using System.Linq; -using System.Text.RegularExpressions; -using System.Web; -using System.Web.Security; - using DotNetNuke.Common; using DotNetNuke.Common.Utilities; using DotNetNuke.Data; @@ -44,10 +34,18 @@ using DotNetNuke.Entities.Users.Social; using DotNetNuke.Instrumentation; using DotNetNuke.Services.Exceptions; -using DotNetNuke.Services.Log.EventLog; //DNN-4016 -using DotNetNuke.Services.Authentication; using DotNetNuke.Services.Localization; +using DotNetNuke.Services.Log.EventLog; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Configuration.Provider; +using System.Data; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web; +using System.Web.Security; #endregion @@ -67,12 +65,12 @@ namespace DotNetNuke.Security.Membership /// ----------------------------------------------------------------------------- public class AspNetMembershipProvider : MembershipProvider { - private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof (AspNetMembershipProvider)); + private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(AspNetMembershipProvider)); #region Private Members private readonly DataProvider _dataProvider = DataProvider.Instance(); - private readonly IEnumerable _socialAuthProviders = new List() {"Facebook", "Google", "Twitter", "LiveID"}; + private readonly IEnumerable _socialAuthProviders = new List() { "Facebook", "Google", "Twitter", "LiveID" }; #endregion @@ -202,7 +200,7 @@ private static bool AutoUnlockUser(MembershipUser aspNetUser) { if (Host.AutoAccountUnlockDuration != 0) { - if (aspNetUser.LastLockoutDate < DateTime.Now.AddMinutes(-1*Host.AutoAccountUnlockDuration)) + if (aspNetUser.LastLockoutDate < DateTime.Now.AddMinutes(-1 * Host.AutoAccountUnlockDuration)) { //Unlock user in Data Store if (aspNetUser.UnlockUser()) @@ -423,16 +421,16 @@ private static UserInfo FillUserAndProfile(int portalId, IDataReader dr) if (bContinue) { user = new UserInfo - { - PortalID = Null.SetNullInteger(dr["PortalID"]), - IsSuperUser = Null.SetNullBoolean(dr["IsSuperUser"]), - IsDeleted = Null.SetNullBoolean(dr["IsDeleted"]), - UserID = Null.SetNullInteger(dr["UserID"]), - DisplayName = Null.SetNullString(dr["DisplayName"]), - Username = Null.SetNullString(dr["Username"]), - Email = Null.SetNullString(dr["Email"]), - AffiliateID = Null.SetNullInteger(dr["AffiliateID"]) - }; + { + PortalID = Null.SetNullInteger(dr["PortalID"]), + IsSuperUser = Null.SetNullBoolean(dr["IsSuperUser"]), + IsDeleted = Null.SetNullBoolean(dr["IsDeleted"]), + UserID = Null.SetNullInteger(dr["UserID"]), + DisplayName = Null.SetNullString(dr["DisplayName"]), + Username = Null.SetNullString(dr["Username"]), + Email = Null.SetNullString(dr["Email"]), + AffiliateID = Null.SetNullInteger(dr["AffiliateID"]) + }; user.AffiliateID = Null.SetNullInteger(Null.SetNull(dr["AffiliateID"], user.AffiliateID)); UserController.GetUserMembership(user); @@ -509,13 +507,16 @@ private static UserInfo FillUserInfo(int portalId, IDataReader dr, bool closeDat if (bContinue) { user = new UserInfo - { - PortalID = Null.SetNullInteger(dr["PortalID"]), - IsSuperUser = Null.SetNullBoolean(dr["IsSuperUser"]), - UserID = Null.SetNullInteger(dr["UserID"]), - DisplayName = Null.SetNullString(dr["DisplayName"]), - LastIPAddress = Null.SetNullString(dr["LastIPAddress"]) - }; + { + PortalID = Null.SetNullInteger(dr["PortalID"]), + IsSuperUser = Null.SetNullBoolean(dr["IsSuperUser"]), + UserID = Null.SetNullInteger(dr["UserID"]), + DisplayName = Null.SetNullString(dr["DisplayName"]), + LastIPAddress = Null.SetNullString(dr["LastIPAddress"]), + HasAgreedToTerms = Null.SetNullBoolean(dr["HasAgreedToTerms"]), + HasAgreedToTermsOn = Null.SetNullDateTime(dr["HasAgreedToTermsOn"]), + RequestsRemoval = Null.SetNullBoolean(dr["RequestsRemoval"]) + }; var schema = dr.GetSchemaTable(); if (schema != null) @@ -632,7 +633,7 @@ private static object GetMembershipUserByUserKeyCallBack(CacheItemArgs cacheItem return System.Web.Security.Membership.GetUser(new Guid(userKey)); } - + public override UserInfo GetUserByAuthToken(int portalId, string userToken, string authType) { IDataReader dr = _dataProvider.GetUserByAuthToken(portalId, userToken, authType); @@ -657,15 +658,15 @@ private static void UpdateUserMembership(UserInfo user) membershipUser.IsApproved = user.Membership.Approved; } - try - { - System.Web.Security.Membership.UpdateUser(membershipUser); - } - catch (ProviderException ex) - { - throw new Exception(Localization.GetExceptionMessage("UpdateUserMembershipFailed", "Asp.net membership update user failed."), ex); - } - + try + { + System.Web.Security.Membership.UpdateUser(membershipUser); + } + catch (ProviderException ex) + { + throw new Exception(Localization.GetExceptionMessage("UpdateUserMembershipFailed", "Asp.net membership update user failed."), ex); + } + DataCache.RemoveCache(GetCacheKey(user.Username)); } @@ -756,10 +757,10 @@ public override void ChangeUsername(int userId, string newUsername) { throw new ArgumentException(Localization.GetExceptionMessage("InvalidUserName", "The username specified is invalid.")); } - + var valid = UserController.Instance.IsValidUserName(userName); - if (!valid) + if (!valid) { throw new ArgumentException(Localization.GetExceptionMessage("InvalidUserName", "The username specified is invalid.")); } @@ -791,7 +792,7 @@ public override void ChangeUsername(int userId, string newUsername) UserController.Instance.GetCurrentUserInfo().UserID, EventLogController.EventLogType.USERNAME_UPDATED); - DataCache.ClearCache(); + DataCache.ClearCache(); } /// ----------------------------------------------------------------------------- @@ -808,13 +809,13 @@ public override void ChangeUsername(int userId, string newUsername) public override bool ChangePassword(UserInfo user, string oldPassword, string newPassword) { MembershipUser aspnetUser = GetMembershipUser(user); - + var m = new MembershipPasswordController(); if (m.IsPasswordInHistory(user.UserID, user.PortalID, newPassword)) { return false; } - + if (string.IsNullOrEmpty(oldPassword)) { aspnetUser.UnlockUser(); @@ -1136,7 +1137,7 @@ public override UserInfo GetUserByDisplayName(int portalId, string displayName) UserInfo objUserInfo = FillUserInfo(portalId, dr, true); return objUserInfo; } - + /// ----------------------------------------------------------------------------- /// /// GetUserByUserName retrieves a User from the DataStore. Supports user caching in memory cache. @@ -1439,34 +1440,34 @@ public override ArrayList GetUsersByUserName(int portalId, string userNameToMatc includeDeleted, superUsersOnly), ref totalRecords); } - /// ----------------------------------------------------------------------------- - /// - /// GetUsersByDisplayName gets all the users of the portal whose display name matches a provided - /// filter expression - /// - /// If all records are required, (ie no paging) set pageSize = -1 - /// The Id of the Portal - /// The display name to use to find a match. - /// The page of records to return. - /// The size of the page - /// The total no of records that satisfy the criteria. - /// Include deleted users. - /// Only select super users. - /// An ArrayList of UserInfo objects. - /// ----------------------------------------------------------------------------- - public override ArrayList GetUsersByDisplayName(int portalId, string nameToMatch, int pageIndex, int pageSize, - ref int totalRecords, bool includeDeleted, bool superUsersOnly) - { - if (pageIndex == -1) - { - pageIndex = 0; - pageSize = int.MaxValue; - } - - return FillUserCollection(portalId, - _dataProvider.GetUsersByDisplayname(portalId, nameToMatch, pageIndex, pageSize, - includeDeleted, superUsersOnly), ref totalRecords); - } + /// ----------------------------------------------------------------------------- + /// + /// GetUsersByDisplayName gets all the users of the portal whose display name matches a provided + /// filter expression + /// + /// If all records are required, (ie no paging) set pageSize = -1 + /// The Id of the Portal + /// The display name to use to find a match. + /// The page of records to return. + /// The size of the page + /// The total no of records that satisfy the criteria. + /// Include deleted users. + /// Only select super users. + /// An ArrayList of UserInfo objects. + /// ----------------------------------------------------------------------------- + public override ArrayList GetUsersByDisplayName(int portalId, string nameToMatch, int pageIndex, int pageSize, + ref int totalRecords, bool includeDeleted, bool superUsersOnly) + { + if (pageIndex == -1) + { + pageIndex = 0; + pageSize = int.MaxValue; + } + + return FillUserCollection(portalId, + _dataProvider.GetUsersByDisplayname(portalId, nameToMatch, pageIndex, pageSize, + includeDeleted, superUsersOnly), ref totalRecords); + } /// ----------------------------------------------------------------------------- /// @@ -1539,7 +1540,7 @@ public override bool IsUserOnline(UserInfo user) if (objUsersOnline.IsEnabled()) { Hashtable userList = objUsersOnline.GetUserList(); - var onlineUser = (OnlineUserInfo) userList[user.UserID.ToString()]; + var onlineUser = (OnlineUserInfo)userList[user.UserID.ToString()]; if (onlineUser != null) { isOnline = true; @@ -1611,28 +1612,28 @@ public override string ResetPassword(UserInfo user, string passwordAnswer) /// method does not support RequiresQuestionAndAnswer /// /// - public override bool ResetAndChangePassword(UserInfo user,string newPassword) + public override bool ResetAndChangePassword(UserInfo user, string newPassword) { - return ResetAndChangePassword(user, newPassword, string.Empty); + return ResetAndChangePassword(user, newPassword, string.Empty); } - public override bool ResetAndChangePassword(UserInfo user, string newPassword, string answer) - { - if (RequiresQuestionAndAnswer && string.IsNullOrEmpty(answer)) - { - return false; - } + public override bool ResetAndChangePassword(UserInfo user, string newPassword, string answer) + { + if (RequiresQuestionAndAnswer && string.IsNullOrEmpty(answer)) + { + return false; + } - //Get AspNet MembershipUser - MembershipUser aspnetUser = GetMembershipUser(user); + //Get AspNet MembershipUser + MembershipUser aspnetUser = GetMembershipUser(user); if (aspnetUser.IsLockedOut) { aspnetUser.UnlockUser(); } string resetPassword = ResetPassword(user, answer); - return aspnetUser.ChangePassword(resetPassword, newPassword); - } + return aspnetUser.ChangePassword(resetPassword, newPassword); + } public override bool RestoreUser(UserInfo user) { @@ -1672,6 +1673,50 @@ public override bool UnLockUser(UserInfo user) return retValue; } + /// ----------------------------------------------------------------------------- + /// + /// User has agreed to terms and conditions for the portal + /// + /// + /// + /// The agreeing user. + /// ----------------------------------------------------------------------------- + public override void UserAgreedToTerms(UserInfo user) + { + _dataProvider.UserAgreedToTerms(user.PortalID, user.UserID); + } + + /// ----------------------------------------------------------------------------- + /// + /// Reset all agreements on portal so all users need to agree again at next login + /// + /// + /// + /// Portal for which to reset agreements. + /// ----------------------------------------------------------------------------- + public override void ResetTermsAgreement(int portalId) + { + _dataProvider.ResetTermsAgreement(portalId); + } + + private static Random random = new Random(); + private string RandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } + + /// + /// Sets a boolean on the user portal to indicate this user has requested that their account be deleted + /// + /// User requesting removal + /// True if user requests removal, false if the value needs to be reset. + public override void UserRequestsRemoval(UserInfo user, bool remove) + { + _dataProvider.UserRequestsRemoval(user.PortalID, user.UserID, remove); + } + /// ----------------------------------------------------------------------------- /// /// UpdateUser persists a user to the Data Store @@ -1799,7 +1844,7 @@ public override UserInfo UserLogin(int portalId, string username, string passwor //Initialise Login Status to Failure loginStatus = UserLoginStatus.LOGIN_FAILURE; - + DataCache.ClearUserCache(portalId, username); DataCache.ClearCache(GetCacheKey(username)); @@ -1825,23 +1870,23 @@ public override UserInfo UserLogin(int portalId, string username, string passwor } else { - loginStatus = UserLoginStatus.LOGIN_USERLOCKEDOUT; + loginStatus = UserLoginStatus.LOGIN_USERLOCKEDOUT; } } //Check in a verified situation whether the user is Approved if (user.Membership.Approved == false && user.IsSuperUser == false) { - + //Check Verification code (skip for FB, Google, Twitter, LiveID as it has no verification code) - if (_socialAuthProviders.Contains(authType) && String.IsNullOrEmpty(verificationCode)) + if (_socialAuthProviders.Contains(authType) && String.IsNullOrEmpty(verificationCode)) { if (PortalController.Instance.GetCurrentPortalSettings().UserRegistration == - (int) Globals.PortalRegistrationType.PublicRegistration) + (int)Globals.PortalRegistrationType.PublicRegistration) { user.Membership.Approved = true; UserController.UpdateUser(portalId, user); - UserController.ApproveUser(user); + UserController.ApproveUser(user); } else { @@ -1861,7 +1906,7 @@ public override UserInfo UserLogin(int portalId, string username, string passwor } } - } + } //Verify User Credentials bool bValid = false; diff --git a/DNN Platform/Library/Security/Membership/MembershipProvider.cs b/DNN Platform/Library/Security/Membership/MembershipProvider.cs index 7cda55f6e38..60ed5adf4f2 100644 --- a/DNN Platform/Library/Security/Membership/MembershipProvider.cs +++ b/DNN Platform/Library/Security/Membership/MembershipProvider.cs @@ -75,6 +75,9 @@ public static MembershipProvider Instance() public abstract void GetUserMembership(ref UserInfo user); public abstract string ResetPassword(UserInfo user, string passwordAnswer); public abstract bool UnLockUser(UserInfo user); + public abstract void UserAgreedToTerms(UserInfo user); + public abstract void ResetTermsAgreement(int portalId); + public abstract void UserRequestsRemoval(UserInfo user, bool remove); public abstract void UpdateUser(UserInfo user); public abstract UserInfo UserLogin(int portalId, string username, string password, string verificationCode, ref UserLoginStatus loginStatus); public abstract UserInfo UserLogin(int portalId, string username, string password, string authType, string verificationCode, ref UserLoginStatus loginStatus); diff --git a/DNN Platform/Library/Security/Membership/UserValidStatus.cs b/DNN Platform/Library/Security/Membership/UserValidStatus.cs index d4be15dce16..81d58029e77 100644 --- a/DNN Platform/Library/Security/Membership/UserValidStatus.cs +++ b/DNN Platform/Library/Security/Membership/UserValidStatus.cs @@ -26,6 +26,7 @@ public enum UserValidStatus PASSWORDEXPIRED = 1, PASSWORDEXPIRING = 2, UPDATEPROFILE = 3, - UPDATEPASSWORD = 4 + UPDATEPASSWORD = 4, + MUSTAGREETOTERMS = 5 } } \ No newline at end of file diff --git a/DNN Platform/Library/Security/Profile/ProfileProvider.cs b/DNN Platform/Library/Security/Profile/ProfileProvider.cs index 8a592b51278..bcb80626683 100644 --- a/DNN Platform/Library/Security/Profile/ProfileProvider.cs +++ b/DNN Platform/Library/Security/Profile/ProfileProvider.cs @@ -50,7 +50,7 @@ public static ProfileProvider Instance() public abstract void GetUserProfile(ref UserInfo user); public abstract void UpdateUserProfile(UserInfo user); - + #endregion } } \ No newline at end of file diff --git a/DNN Platform/Library/Security/Roles/RoleController.cs b/DNN Platform/Library/Security/Roles/RoleController.cs index 583c0d2c88c..9316778553a 100644 --- a/DNN Platform/Library/Security/Roles/RoleController.cs +++ b/DNN Platform/Library/Security/Roles/RoleController.cs @@ -654,6 +654,22 @@ public static bool DeleteUserRole(UserInfo objUser, RoleInfo role, PortalSetting return canDelete; } + /// + /// Completely remove all a user's roles for a specific portal. This method is used when + /// anonymizing a user + /// + /// User for which all roles must be deleted. The PortalId property + /// is used to determine for which portal roles must be removed. + internal static void DeleteUserRoles(UserInfo user) + { + var ctrl = new RoleController(); + var userRoles = ctrl.GetUserRoles(user, true); + foreach (var ur in userRoles.Where(r => r.PortalID == user.PortalID)) + { + provider.RemoveUserFromRole(user.PortalID, user, ur); + } + } + /// ----------------------------------------------------------------------------- /// /// Fetch a single RoleGroup diff --git a/DNN Platform/Library/Services/Users/PurgeDeletedUsers.cs b/DNN Platform/Library/Services/Users/PurgeDeletedUsers.cs new file mode 100644 index 00000000000..5aa919550a5 --- /dev/null +++ b/DNN Platform/Library/Services/Users/PurgeDeletedUsers.cs @@ -0,0 +1,94 @@ +#region Copyright +// +// DotNetNuke® - http://www.dotnetnuke.com +// Copyright (c) 2002-2018 +// by DotNetNuke Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +// to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions +// of the Software. +// ********************************** +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#endregion +#region Usings + +using DotNetNuke.Entities.Portals; +using DotNetNuke.Entities.Users; +using DotNetNuke.Services.Scheduling; +using System; + +#endregion + + +namespace DotNetNuke.Services.Users +{ + public class PurgeDeletedUsers : SchedulerClient + { + + public PurgeDeletedUsers(ScheduleHistoryItem objScheduleHistoryItem) + { + ScheduleHistoryItem = objScheduleHistoryItem; + } + + public override void DoWork() + { + try + { + foreach (PortalInfo portal in new PortalController().GetPortals()) + { + var settings = new PortalSettings(portal.PortalID); + if (settings.DataConsentActive) + { + if (settings.DataConsentUserDeleteAction == PortalSettings.UserDeleteAction.DelayedHardDelete) + { + var thresholdDate = DateTime.Now; + switch (settings.DataConsentDelayMeasurement) + { + case "h": + thresholdDate = DateTime.Now.AddHours(-1 * settings.DataConsentDelay); + break; + case "d": + thresholdDate = DateTime.Now.AddDays(-1 * settings.DataConsentDelay); + break; + case "w": + thresholdDate = DateTime.Now.AddDays(-7 * settings.DataConsentDelay); + break; + } + var deletedUsers = UserController.GetDeletedUsers(portal.PortalID); + foreach (UserInfo user in deletedUsers) + { + if (user.LastModifiedOnDate < thresholdDate && user.RequestsRemoval) + { + UserController.RemoveUser(user); + ScheduleHistoryItem.AddLogNote(string.Format("Removed user {0}{1}", user.Username, Environment.NewLine)); + } + } + } + } + } + ScheduleHistoryItem.Succeeded = true; //REQUIRED + ScheduleHistoryItem.AddLogNote("Purging deleted users task completed"); + } + catch (Exception exc) //REQUIRED + { + ScheduleHistoryItem.Succeeded = false; //REQUIRED + + ScheduleHistoryItem.AddLogNote(string.Format("Purging deleted users task failed: {0}.", exc.ToString())); + + //notification that we have errored + Errored(ref exc); //REQUIRED + + //log the exception + Exceptions.Exceptions.LogException(exc); //OPTIONAL + } + } + } +} diff --git a/Website/DesktopModules/Admin/Authentication/App_LocalResources/Login.ascx.resx b/Website/DesktopModules/Admin/Authentication/App_LocalResources/Login.ascx.resx index 133b0480d39..9040e63771d 100644 --- a/Website/DesktopModules/Admin/Authentication/App_LocalResources/Login.ascx.resx +++ b/Website/DesktopModules/Admin/Authentication/App_LocalResources/Login.ascx.resx @@ -112,10 +112,10 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 You can associate your login information with an existing user account by entering your user name and password. @@ -231,4 +231,10 @@ This account has been locked out after too many unsuccessful login attempts. Please contact the Site Administrator. + + You must agree to our terms to continue + + + Could not remove the account. Please contact the site adminstrator and let him/her know that you wish to be removed. + \ No newline at end of file diff --git a/Website/DesktopModules/Admin/Authentication/Login.ascx b/Website/DesktopModules/Admin/Authentication/Login.ascx index 1a3c365779b..e187ce5dc34 100644 --- a/Website/DesktopModules/Admin/Authentication/Login.ascx +++ b/Website/DesktopModules/Admin/Authentication/Login.ascx @@ -5,6 +5,7 @@ <%@ Register TagPrefix="dnn" TagName="Profile" Src="~/DesktopModules/Admin/Security/Profile.ascx" %> <%@ Register TagPrefix="dnn" TagName="Password" Src="~/DesktopModules/Admin/Security/Password.ascx" %> <%@ Register TagPrefix="dnn" TagName="User" Src="~/DesktopModules/Admin/Security/User.ascx" %> +<%@ Register TagPrefix="dnn" TagName="DataConsent" Src="~/DesktopModules/Admin/Security/DataConsent.ascx" %>
@@ -77,4 +78,7 @@ + + +
\ No newline at end of file diff --git a/Website/DesktopModules/Admin/Authentication/Login.ascx.cs b/Website/DesktopModules/Admin/Authentication/Login.ascx.cs index dc015bb2530..f695453ab0c 100644 --- a/Website/DesktopModules/Admin/Authentication/Login.ascx.cs +++ b/Website/DesktopModules/Admin/Authentication/Login.ascx.cs @@ -20,17 +20,6 @@ #endregion #region Usings -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Globalization; -using System.IO; -using System.Text.RegularExpressions; -using System.Web; -using System.Web.UI; -using System.Web.UI.HtmlControls; -using System.Web.UI.WebControls; using DotNetNuke.Common; using DotNetNuke.Common.Utilities; using DotNetNuke.Entities.Host; @@ -54,6 +43,17 @@ using DotNetNuke.UI.Skins.Controls; using DotNetNuke.UI.UserControls; using DotNetNuke.UI.WebControls; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Text.RegularExpressions; +using System.Web; +using System.Web.UI; +using System.Web.UI.HtmlControls; +using System.Web.UI.WebControls; #endregion @@ -202,13 +202,13 @@ protected string RedirectURL var comparison = StringComparison.InvariantCultureIgnoreCase; // we need .TrimEnd('/') because a portlalias for a specific culture will not have a trailing /, while a returnurl will. var isDefaultPage = redirectURL == "/" - || (alias.Contains("/") && redirectURL.TrimEnd('/').Equals(alias.Substring(alias.IndexOf("/", comparison)), comparison)); - + || (alias.Contains("/") && redirectURL.TrimEnd('/').Equals(alias.Substring(alias.IndexOf("/", comparison)), comparison)); + if (string.IsNullOrEmpty(redirectURL) || isDefaultPage) { if ( - NeedRedirectAfterLogin - && (isDefaultPage || IsRedirectingFromLoginUrl()) + NeedRedirectAfterLogin + && (isDefaultPage || IsRedirectingFromLoginUrl()) && Convert.ToInt32(setting) != Null.NullInteger ) { @@ -262,12 +262,12 @@ protected string RedirectURL private bool IsRedirectingFromLoginUrl() { - return Request.UrlReferrer != null && + return Request.UrlReferrer != null && Request.UrlReferrer.LocalPath.ToLowerInvariant().EndsWith(LOGIN_PATH); } - private bool NeedRedirectAfterLogin => - LoginStatus == UserLoginStatus.LOGIN_SUCCESS + private bool NeedRedirectAfterLogin => + LoginStatus == UserLoginStatus.LOGIN_SUCCESS || LoginStatus == UserLoginStatus.LOGIN_SUPERUSER || LoginStatus == UserLoginStatus.LOGIN_INSECUREHOSTPASSWORD || LoginStatus == UserLoginStatus.LOGIN_INSECUREADMINPASSWORD; @@ -355,28 +355,28 @@ protected string UserToken { ViewState["UserToken"] = value; } + } + + /// + /// Gets and sets the current UserName + /// + protected string UserName + { + get + { + var userName = ""; + if (ViewState["UserName"] != null) + { + userName = Convert.ToString(ViewState["UserName"]); + } + return userName; + } + set + { + ViewState["UserName"] = value; + } } - /// - /// Gets and sets the current UserName - /// - protected string UserName - { - get - { - var userName = ""; - if (ViewState["UserName"] != null) - { - userName = Convert.ToString(ViewState["UserName"]); - } - return userName; - } - set - { - ViewState["UserName"] = value; - } - } - #endregion #region Private Methods @@ -469,9 +469,11 @@ private void BindLogin() else { //if there are social authprovider only - if (_oAuthControls.Count == 0) - //Portal has no login controls enabled so load default DNN control - DisplayLoginControl(defaultLoginControl, false, false); + if (_oAuthControls.Count == 0) + { + //Portal has no login controls enabled so load default DNN control + DisplayLoginControl(defaultLoginControl, false, false); + } } break; case 1: @@ -486,7 +488,11 @@ private void BindLogin() break; default: //make sure defaultAuth provider control is diplayed first - if (_defaultauthLogin.Count > 0) DisplayTabbedLoginControl(_defaultauthLogin[0], tsLogin.Tabs); + if (_defaultauthLogin.Count > 0) + { + DisplayTabbedLoginControl(_defaultauthLogin[0], tsLogin.Tabs); + } + foreach (AuthenticationLoginBase authLoginControl in _loginControls) { DisplayTabbedLoginControl(authLoginControl, tsLogin.Tabs); @@ -744,11 +750,13 @@ private void ShowPanel() bool showRegister = (PageNo == 1); bool showPassword = (PageNo == 2); bool showProfile = (PageNo == 3); + bool showDataConsent = (PageNo == 4); pnlProfile.Visible = showProfile; pnlPassword.Visible = showPassword; pnlLogin.Visible = showLogin; pnlRegister.Visible = showRegister; pnlAssociate.Visible = showRegister; + pnlDataConsent.Visible = showDataConsent; switch (PageNo) { case 0: @@ -764,6 +772,10 @@ private void ShowPanel() case 3: ctlProfile.UserId = UserId; ctlProfile.DataBind(); + break; + case 4: + ctlDataConsent.UserId = UserId; + ctlDataConsent.DataBind(); break; } @@ -932,8 +944,26 @@ private void ValidateUser(UserInfo objUser, bool ignoreExpiring) AddModuleMessage("ProfileUpdate", ModuleMessage.ModuleMessageType.YellowWarning, true); PageNo = 3; break; + case UserValidStatus.MUSTAGREETOTERMS: + if (PortalSettings.DataConsentConsentRedirect == -1) + { + UserId = objUser.UserID; + AddModuleMessage("MustConsent", ModuleMessage.ModuleMessageType.YellowWarning, true); + PageNo = 4; + } + else + { + // Use the reset password token to identify the user during the redirect + UserController.ResetPasswordToken(objUser); + objUser = UserController.GetUserById(objUser.PortalID, objUser.UserID); + Response.Redirect(Globals.NavigateURL(PortalSettings.DataConsentConsentRedirect, "", string.Format("token={0}", objUser.PasswordResetToken))); + } + break; } - if (okToShowPanel) ShowPanel(); + if (okToShowPanel) + { + ShowPanel(); + } } private bool UserNeedsVerification() @@ -966,16 +996,20 @@ protected override void OnInit(EventArgs e) ctlPassword.PasswordUpdated += PasswordUpdated; ctlProfile.ProfileUpdated += ProfileUpdated; ctlUser.UserCreateCompleted += UserCreateCompleted; + ctlDataConsent.DataConsentCompleted += DataConsentCompleted; //Set the User Control Properties ctlUser.ID = "User"; - //Set the Profile Control Properties + //Set the Password Control Properties ctlPassword.ID = "Password"; //Set the Profile Control Properties ctlProfile.ID = "Profile"; + //Set the Data Consent Control Properties + ctlDataConsent.ID = "DataConsent"; + //Override the redirected page title if page has loaded with ctl=Login if (Request.QueryString["ctl"] != null) { @@ -1179,6 +1213,28 @@ protected void PasswordUpdated(object sender, Password.PasswordUpdatedEventArgs } } + /// + /// DataConsentCompleted runs after the user has gone through the data consent screen + /// + /// + /// + protected void DataConsentCompleted(object sender, DataConsent.DataConsentEventArgs e) + { + switch (e.Status) + { + case DataConsent.DataConsentStatus.Consented: + ValidateUser(ctlDataConsent.User, true); + break; + case DataConsent.DataConsentStatus.Cancelled: + case DataConsent.DataConsentStatus.RemovedAccount: + Response.Redirect(Globals.NavigateURL(PortalSettings.HomeTabId), true); + break; + case DataConsent.DataConsentStatus.FailedToRemoveAccount: + AddModuleMessage("FailedToRemoveAccount", ModuleMessage.ModuleMessageType.RedError, true); + break; + } + } + /// /// ProfileUpdated runs when the profile is updated /// diff --git a/Website/DesktopModules/Admin/Authentication/Login.ascx.designer.cs b/Website/DesktopModules/Admin/Authentication/Login.ascx.designer.cs index 239ee1089c7..64b4a5c959f 100644 --- a/Website/DesktopModules/Admin/Authentication/Login.ascx.designer.cs +++ b/Website/DesktopModules/Admin/Authentication/Login.ascx.designer.cs @@ -299,5 +299,23 @@ public partial class Login { /// To modify move field declaration from designer file to code-behind file. /// protected global::DesktopModules.Admin.Security.DNNProfile ctlProfile; + + /// + /// pnlDataConsent control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlDataConsent; + + /// + /// ctlDataConsent control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::DotNetNuke.Modules.Admin.Users.DataConsent ctlDataConsent; } } diff --git a/Website/DesktopModules/Admin/Security/App_LocalResources/DataConsent.ascx.resx b/Website/DesktopModules/Admin/Security/App_LocalResources/DataConsent.ascx.resx new file mode 100644 index 00000000000..b9b945bc648 --- /dev/null +++ b/Website/DesktopModules/Admin/Security/App_LocalResources/DataConsent.ascx.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Your personal data will be anonymized. After anonymization the data can no longer be traced to you personally and you will no longer be able to log in using this account. Do you wish to continue? + + + Delete Me + + + Submit + + + I agree to the <a href="{0}">Terms of Use</a> of this website and its <a href="{1}">Privacy Statement</a> + + + Your personal data will be removed from this site shortly. After it has been removed it can no longer be recovered and your data will be lost forever. You will also no longer be able to log in using this account. Do you wish to continue? + + + Your personal data will be removed from this site immediately. After it has been removed it can no longer be recovered and your data will be lost forever. You will also no longer be able to log in using this account. Do you wish to continue? + + + We will work to remove your personal data this site shortly. After it has been removed it can no longer be recovered and your data will be lost forever. You will also no longer be able to log in using this account. Do you wish to continue? + + \ No newline at end of file diff --git a/Website/DesktopModules/Admin/Security/DataConsent.ascx b/Website/DesktopModules/Admin/Security/DataConsent.ascx new file mode 100644 index 00000000000..6a099a7d776 --- /dev/null +++ b/Website/DesktopModules/Admin/Security/DataConsent.ascx @@ -0,0 +1,32 @@ +<%@ Control Language="C#" AutoEventWireup="false" CodeBehind="DataConsent.ascx.cs" Inherits="DotNetNuke.Modules.Admin.Users.DataConsent" %> +<%@ Register TagPrefix="dnn" TagName="Label" Src="~/controls/LabelControl.ascx" %> +<%@ Register TagPrefix="dnn" Assembly="DotNetNuke" Namespace="DotNetNuke.UI.WebControls" %> +
+
+ + <%= String.Format(DotNetNuke.Services.Localization.Localization.GetString("DataConsent", LocalResourceFile), + PortalSettings.Current.TermsTabId == Null.NullInteger ? DotNetNuke.Common.Globals.NavigateURL(PortalSettings.Current.ActiveTab.TabID, "Terms") : DotNetNuke.Common.Globals.NavigateURL(PortalSettings.Current.TermsTabId), + PortalSettings.Current.PrivacyTabId == Null.NullInteger ? DotNetNuke.Common.Globals.NavigateURL(PortalSettings.Current.ActiveTab.TabID, "Privacy") : DotNetNuke.Common.Globals.NavigateURL(PortalSettings.Current.PrivacyTabId)) %> +
+ + +
    +
  • +
  • +
  • +
  • +
  • +
  • +
+
+ + \ No newline at end of file diff --git a/Website/DesktopModules/Admin/Security/DataConsent.ascx.cs b/Website/DesktopModules/Admin/Security/DataConsent.ascx.cs new file mode 100644 index 00000000000..2cc64a430a1 --- /dev/null +++ b/Website/DesktopModules/Admin/Security/DataConsent.ascx.cs @@ -0,0 +1,162 @@ +#region Copyright +// +// DotNetNuke® - http://www.dotnetnuke.com +// Copyright (c) 2002-2018 +// by DotNetNuke Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +// to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#endregion +#region Usings + +using DotNetNuke.Entities.Modules; +using DotNetNuke.Entities.Portals; +using DotNetNuke.Entities.Users; +using DotNetNuke.Security; +using DotNetNuke.Services.Log.EventLog; +using System; +using System.Web.UI; + +#endregion + +namespace DotNetNuke.Modules.Admin.Users +{ + public partial class DataConsent : UserModuleBase + { + public string DeleteMeConfirmString + { + get + { + switch (PortalSettings.DataConsentUserDeleteAction) + { + case PortalSettings.UserDeleteAction.Manual: + return LocalizeString("ManualDelete.Confirm"); + case PortalSettings.UserDeleteAction.DelayedHardDelete: + return LocalizeString("DelayedHardDelete.Confirm"); + case PortalSettings.UserDeleteAction.HardDelete: + return LocalizeString("HardDelete.Confirm"); + } + return ""; + } + } + + #region Delegate and event + + public delegate void DataConsentEventHandler(object sender, DataConsentEventArgs e); + public event DataConsentEventHandler DataConsentCompleted; + public void OnDataConsentComplete(DataConsentEventArgs e) + { + DataConsentCompleted?.Invoke(this, e); + } + + #endregion + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + cmdCancel.Click += cmdCancel_Click; + cmdSubmit.Click += cmdSubmit_Click; + cmdDeleteMe.Click += cmdDeleteMe_Click; + cmdDeleteMe.Visible = PortalSettings.DataConsentUserDeleteAction != PortalSettings.UserDeleteAction.Off; + if (!Page.IsPostBack) + { + chkAgree.Checked = false; + cmdSubmit.Enabled = false; + pnlNoAgreement.Visible = false; + } + } + private void cmdCancel_Click(object sender, EventArgs e) + { + OnDataConsentComplete(new DataConsentEventArgs(DataConsentStatus.Cancelled)); + } + + private void cmdSubmit_Click(object sender, EventArgs e) + { + if (chkAgree.Checked) + { + UserController.UserAgreedToTerms(User); + User.HasAgreedToTerms = true; + OnDataConsentComplete(new DataConsentEventArgs(DataConsentStatus.Consented)); + } + } + + private void cmdDeleteMe_Click(object sender, EventArgs e) + { + var success = false; + switch (PortalSettings.DataConsentUserDeleteAction) + { + case PortalSettings.UserDeleteAction.Manual: + User.Membership.Approved = false; + UserController.UpdateUser(PortalSettings.PortalId, User); + UserController.UserRequestsRemoval(User, true); + success = true; + break; + case PortalSettings.UserDeleteAction.DelayedHardDelete: + var user = User; + success = UserController.DeleteUser(ref user, true, false); + UserController.UserRequestsRemoval(User, true); + break; + case PortalSettings.UserDeleteAction.HardDelete: + success = UserController.RemoveUser(User); + break; + } + if (success) + { + PortalSecurity.Instance.SignOut(); + OnDataConsentComplete(new DataConsentEventArgs(DataConsentStatus.RemovedAccount)); + } + else + { + OnDataConsentComplete(new DataConsentEventArgs(DataConsentStatus.FailedToRemoveAccount)); + } + } + + #region DataConsentEventArgs + + /// ----------------------------------------------------------------------------- + /// + /// The DataConsentEventArgs class provides a customised EventArgs class for + /// the DataConsent Event + /// + public class DataConsentEventArgs + { + /// ----------------------------------------------------------------------------- + /// + /// Constructs a new DataConsentEventArgs + /// + /// The Data Consent Status + public DataConsentEventArgs(DataConsentStatus status) + { + Status = status; + } + + /// ----------------------------------------------------------------------------- + /// + /// Gets and sets the Update Status + /// + public DataConsentStatus Status { get; set; } + } + + public enum DataConsentStatus + { + Consented, + Cancelled, + RemovedAccount, + FailedToRemoveAccount + } + + #endregion + + } +} \ No newline at end of file diff --git a/Website/DesktopModules/Admin/Security/DataConsent.ascx.designer.cs b/Website/DesktopModules/Admin/Security/DataConsent.ascx.designer.cs new file mode 100644 index 00000000000..12d67111871 --- /dev/null +++ b/Website/DesktopModules/Admin/Security/DataConsent.ascx.designer.cs @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DotNetNuke.Modules.Admin.Users { + + + public partial class DataConsent { + + /// + /// chkAgree control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CheckBox chkAgree; + + /// + /// pnlNoAgreement control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlNoAgreement; + + /// + /// cmdSubmit control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button cmdSubmit; + + /// + /// cmdCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button cmdCancel; + + /// + /// cmdDeleteMe control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button cmdDeleteMe; + } +} diff --git a/Website/DotNetNuke.Website.csproj b/Website/DotNetNuke.Website.csproj index ffbe38f4e3f..c8bb98b6912 100644 --- a/Website/DotNetNuke.Website.csproj +++ b/Website/DotNetNuke.Website.csproj @@ -633,6 +633,13 @@ SearchResults.ascx + + DataConsent.ascx + ASPXCodeBehind + + + DataConsent.ascx + EditUser.ascx ASPXCodeBehind @@ -798,6 +805,7 @@ + @@ -3295,6 +3303,7 @@ + @@ -3363,6 +3372,9 @@ + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/Website/Providers/DataProviders/SqlDataProvider/09.04.00.SqlDataProvider b/Website/Providers/DataProviders/SqlDataProvider/09.04.00.SqlDataProvider index 026cfce0b2a..addf21fc178 100644 --- a/Website/Providers/DataProviders/SqlDataProvider/09.04.00.SqlDataProvider +++ b/Website/Providers/DataProviders/SqlDataProvider/09.04.00.SqlDataProvider @@ -8,9 +8,138 @@ /***** *****/ /************************************************************/ - - /************************************************************/ -/***** SqlDataProvider *****/ +/***** Data Consent Changes *****/ /************************************************************/ + +ALTER TABLE {databaseOwner}{objectQualifier}UserPortals ADD + HasAgreedToTerms BIT NOT NULL DEFAULT(0), + HasAgreedToTermsOn DATETIME NULL, + RequestsRemoval BIT NOT NULL DEFAULT(0) +GO + +IF EXISTS (SELECT * FROM Sys.Views WHERE (name = N'{objectQualifier}vw_Users')) + DROP VIEW {databaseOwner}[{objectQualifier}vw_Users] +GO + +CREATE VIEW {databaseOwner}[{objectQualifier}vw_Users] +AS + SELECT U.UserId, + UP.PortalId, + U.Username, + U.FirstName, + U.LastName, + U.DisplayName, + U.IsSuperUser, + U.Email, + UP.VanityUrl, + U.AffiliateId, + IsNull(UP.IsDeleted, U.IsDeleted) AS IsDeleted, + UP.RefreshRoles, + U.LastIPAddress, + U.UpdatePassword, + U.PasswordResetToken, + U.PasswordResetExpiration, + UP.Authorised, + UP.HasAgreedToTerms, + UP.HasAgreedToTermsOn, + UP.RequestsRemoval, + U.CreatedByUserId, + U.CreatedOnDate, + U.LastModifiedByUserId, + U.LastModifiedOnDate + FROM {databaseOwner}[{objectQualifier}Users] AS U + LEFT JOIN {databaseOwner}[{objectQualifier}UserPortals] AS UP + ON CASE WHEN U.IsSuperuser = 1 THEN 0 ELSE U.UserId END = UP.UserId +GO + +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}GetUsersByRolename]') AND type in (N'P', N'PC')) + DROP PROCEDURE {databaseOwner}[{objectQualifier}GetUsersByRolename] +GO + +CREATE PROCEDURE {databaseOwner}[{objectQualifier}GetUsersByRolename] + @PortalID INT, + @Rolename NVARCHAR(50) +AS + DECLARE @UserPortalId INT + DECLARE @PortalGroupId INT + SELECT @PortalGroupId = PortalGroupId FROM {databaseOwner}[{objectQualifier}Portals] WHERE PortalID = @PortalID + IF EXISTS(SELECT PortalGroupID FROM {databaseOwner}[{objectQualifier}PortalGroups] WHERE PortalGroupID = @PortalGroupId) + BEGIN + SELECT @UserPortalId = MasterPortalID FROM {databaseOwner}[{objectQualifier}PortalGroups] WHERE PortalGroupID = @PortalGroupId + END + ELSE + BEGIN + SELECT @UserPortalId = @PortalID + END + SELECT + U.*, + UP.PortalId, + UP.Authorised, + UP.HasAgreedToTerms, + UP.HasAgreedToTermsOn, + UP.RequestsRemoval, + UP.IsDeleted, + UP.RefreshRoles, + UP.VanityUrl + FROM {databaseOwner}{objectQualifier}UserPortals AS UP + RIGHT OUTER JOIN {databaseOwner}{objectQualifier}UserRoles UR + INNER JOIN {databaseOwner}{objectQualifier}Roles R ON UR.RoleID = R.RoleID + RIGHT OUTER JOIN {databaseOwner}{objectQualifier}Users AS U ON UR.UserID = U.UserID + ON UP.UserId = U.UserID + WHERE ( UP.PortalId = @UserPortalId OR @UserPortalId IS Null ) + AND (UP.IsDeleted = 0 OR UP.IsDeleted Is NULL) + AND (R.RoleName = @Rolename) + AND (R.PortalId = @PortalID OR @PortalID IS Null ) + ORDER BY U.FirstName + ' ' + U.LastName + +GO + +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}UserAgreedToTerms]') AND type in (N'P', N'PC')) + DROP PROCEDURE {databaseOwner}[{objectQualifier}UserAgreedToTerms] +GO + +CREATE PROCEDURE {databaseOwner}[{objectQualifier}UserAgreedToTerms] + @PortalId INT, + @UserId INT +AS + UPDATE {databaseOwner}{objectQualifier}UserPortals + SET HasAgreedToTerms = 1, HasAgreedToTermsOn = GETDATE() + WHERE PortalId = @PortalId AND UserId = @UserId +GO + +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}ResetTermsAgreement]') AND type in (N'P', N'PC')) + DROP PROCEDURE {databaseOwner}[{objectQualifier}ResetTermsAgreement] +GO + +CREATE PROCEDURE {databaseOwner}[{objectQualifier}ResetTermsAgreement] + @PortalId INT +AS + UPDATE {databaseOwner}{objectQualifier}UserPortals + SET HasAgreedToTerms = 0 + WHERE PortalId = @PortalId +GO + +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'{databaseOwner}[{objectQualifier}UserRequestsRemoval]') AND type in (N'P', N'PC')) + DROP PROCEDURE {databaseOwner}[{objectQualifier}UserRequestsRemoval] +GO + +CREATE PROCEDURE {databaseOwner}[{objectQualifier}UserRequestsRemoval] + @PortalId INT, + @UserId INT, + @Remove BIT +AS + UPDATE {databaseOwner}{objectQualifier}UserPortals + SET RequestsRemoval = @Remove + WHERE PortalId = @PortalId AND UserId = @UserId +GO + +/* Add scheduled task to delete soft deleted users */ /************************************************************/ + +IF NOT EXISTS(SELECT ScheduleID FROM {databaseOwner}[{objectQualifier}Schedule] WHERE TypeFullName = 'DotNetNuke.Services.Users.PurgeDeletedUsers, DOTNETNUKE') +BEGIN + INSERT INTO {databaseOwner}[{objectQualifier}Schedule] ([TypeFullName], [TimeLapse], [TimeLapseMeasurement], [RetryTimeLapse], [RetryTimeLapseMeasurement], [RetainHistoryNum], [AttachToEvent], [CatchUpEnabled], [Enabled], [ObjectDependencies], [Servers], [CreatedByUserID], [CreatedOnDate], [LastModifiedByUserID], [LastModifiedOnDate], [FriendlyName]) + VALUES ('DotNetNuke.Services.Users.PurgeDeletedUsers, DOTNETNUKE', 1, 'h', 30, 'm', 10, '', 0, 1, '', NULL, NULL, GETDATE(), NULL, GETDATE(), N'Purge Deleted Users') +END +GO diff --git a/Website/Providers/DataProviders/SqlDataProvider/DotNetNuke.Data.SqlDataProvider b/Website/Providers/DataProviders/SqlDataProvider/DotNetNuke.Data.SqlDataProvider index f3ff900e1dd..bba1dc7cbb1 100644 --- a/Website/Providers/DataProviders/SqlDataProvider/DotNetNuke.Data.SqlDataProvider +++ b/Website/Providers/DataProviders/SqlDataProvider/DotNetNuke.Data.SqlDataProvider @@ -5641,6 +5641,7 @@ INSERT INTO {databaseOwner}[{objectQualifier}Schedule] ([ScheduleID], [TypeFullN INSERT INTO {databaseOwner}[{objectQualifier}Schedule] ([ScheduleID], [TypeFullName], [TimeLapse], [TimeLapseMeasurement], [RetryTimeLapse], [RetryTimeLapseMeasurement], [RetainHistoryNum], [AttachToEvent], [CatchUpEnabled], [Enabled], [ObjectDependencies], [Servers], [CreatedByUserID], [CreatedOnDate], [LastModifiedByUserID], [LastModifiedOnDate], [FriendlyName], [ScheduleStartDate]) VALUES (11, 'DotNetNuke.Services.Social.Messaging.Scheduler.CoreMessagingScheduler, DotNetNuke', 1, 'm', 30, 's', 10, '', 0, 1, 'Messaging', NULL, @CreateBy, @CreateOn, @UpdateBy, @UpdateOn, N'Messaging Dispatch', NULL) INSERT INTO {databaseOwner}[{objectQualifier}Schedule] ([ScheduleID], [TypeFullName], [TimeLapse], [TimeLapseMeasurement], [RetryTimeLapse], [RetryTimeLapseMeasurement], [RetainHistoryNum], [AttachToEvent], [CatchUpEnabled], [Enabled], [ObjectDependencies], [Servers], [CreatedByUserID], [CreatedOnDate], [LastModifiedByUserID], [LastModifiedOnDate], [FriendlyName], [ScheduleStartDate]) VALUES (12, 'DotNetNuke.Services.ClientDependency.PurgeClientDependencyFiles, DOTNETNUKE', 1, 'd', 6, 'h', 5, '', 0, 0, '', NULL, @CreateBy, @CreateOn, @UpdateBy, @UpdateOn, N'Purge Client Dependency Files', NULL) INSERT INTO {databaseOwner}[{objectQualifier}Schedule] ([ScheduleID], [TypeFullName], [TimeLapse], [TimeLapseMeasurement], [RetryTimeLapse], [RetryTimeLapseMeasurement], [RetainHistoryNum], [AttachToEvent], [CatchUpEnabled], [Enabled], [ObjectDependencies], [Servers], [CreatedByUserID], [CreatedOnDate], [LastModifiedByUserID], [LastModifiedOnDate], [FriendlyName], [ScheduleStartDate]) VALUES (13, 'DotNetNuke.Services.OutputCache.PurgeOutputCache, DotNetNuke', 1, 'm', 30, 's', 10, '', 0, 0, 'OutputCache', NULL, @CreateBy, @CreateOn, @UpdateBy, @UpdateOn, N'Purge Output Cache', NULL) +INSERT INTO {databaseOwner}[{objectQualifier}Schedule] ([ScheduleID], [TypeFullName], [TimeLapse], [TimeLapseMeasurement], [RetryTimeLapse], [RetryTimeLapseMeasurement], [RetainHistoryNum], [AttachToEvent], [CatchUpEnabled], [Enabled], [ObjectDependencies], [Servers], [CreatedByUserID], [CreatedOnDate], [LastModifiedByUserID], [LastModifiedOnDate], [FriendlyName], [ScheduleStartDate]) VALUES (14, 'DotNetNuke.Services.Users.PurgeDeletedUsers, DOTNETNUKE', 1, 'h', 30, 'm', 10, '', 0, 1, '', NULL, @CreateBy, @CreateOn, @UpdateBy, @UpdateOn, N'Purge Deleted Users', NULL) SET IDENTITY_INSERT {databaseOwner}[{objectQualifier}Schedule] OFF PRINT(N'Add 369 rows to {databaseOwner}[{objectQualifier}SearchCommonWords]') @@ -6201,6 +6202,8 @@ INSERT INTO {databaseOwner}[{objectQualifier}EventLogConfig] ([ID], [LogTypeKey] INSERT INTO {databaseOwner}[{objectQualifier}EventLogConfig] ([ID], [LogTypeKey], [LogTypePortalID], [LoggingIsActive], [KeepMostRecent], [EmailNotificationIsActive], [NotificationThreshold], [NotificationThresholdTime], [NotificationThresholdTimeType], [MailFromAddress], [MailToAddress]) VALUES (117, N'PAGE_NOT_FOUND_404', NULL, 1, 100, 0, 1, 1, 1, N'', N'') INSERT INTO {databaseOwner}[{objectQualifier}EventLogConfig] ([ID], [LogTypeKey], [LogTypePortalID], [LoggingIsActive], [KeepMostRecent], [EmailNotificationIsActive], [NotificationThreshold], [NotificationThresholdTime], [NotificationThresholdTimeType], [MailFromAddress], [MailToAddress]) VALUES (118, N'IP_LOGIN_BANNED', NULL, 1, 100, 0, 1, 1, 1, N'', N'') INSERT INTO {databaseOwner}[{objectQualifier}EventLogConfig] ([ID], [LogTypeKey], [LogTypePortalID], [LoggingIsActive], [KeepMostRecent], [EmailNotificationIsActive], [NotificationThreshold], [NotificationThresholdTime], [NotificationThresholdTimeType], [MailFromAddress], [MailToAddress]) VALUES (119, N'SCRIPT_COLLISION', NULL, 1, 100, 0, 1, 1, 1, N'', N'') +INSERT INTO {databaseOwner}[{objectQualifier}EventLogConfig] ([ID], [LogTypeKey], [LogTypePortalID], [LoggingIsActive], [KeepMostRecent], [EmailNotificationIsActive], [NotificationThreshold], [NotificationThresholdTime], [NotificationThresholdTimeType], [MailFromAddress], [MailToAddress]) VALUES (120, N'DATA_CONSENT_SUCCESS', NULL, 1, 10, 0, 1, 1, 1, N'', N'') +INSERT INTO {databaseOwner}[{objectQualifier}EventLogConfig] ([ID], [LogTypeKey], [LogTypePortalID], [LoggingIsActive], [KeepMostRecent], [EmailNotificationIsActive], [NotificationThreshold], [NotificationThresholdTime], [NotificationThresholdTimeType], [MailFromAddress], [MailToAddress]) VALUES (121, N'DATA_CONSENT_FAILURE', NULL, 1, 10, 0, 1, 1, 1, N'', N'') SET IDENTITY_INSERT {databaseOwner}[{objectQualifier}EventLogConfig] OFF PRINT(N'Add 73 rows to {databaseOwner}[{objectQualifier}Packages]') diff --git a/Website/Providers/DataProviders/SqlDataProvider/UnInstall.SqlDataProvider b/Website/Providers/DataProviders/SqlDataProvider/UnInstall.SqlDataProvider index 0180928dc55..d40d8211b5b 100644 --- a/Website/Providers/DataProviders/SqlDataProvider/UnInstall.SqlDataProvider +++ b/Website/Providers/DataProviders/SqlDataProvider/UnInstall.SqlDataProvider @@ -1154,6 +1154,12 @@ DROP PROCEDURE {databaseOwner}[{objectQualifier}UpdatePortalAliasOnInstall] GO DROP TABLE {databaseOwner}[{objectQualifier}PortalAlias] GO +DROP PROCEDURE {databaseOwner}[{objectQualifier}UserAgreedToTerms] +GO +DROP PROCEDURE {databaseOwner}[{objectQualifier}ResetTermsAgreement] +GO +DROP PROCEDURE {databaseOwner}[{objectQualifier}UserRequestsRemoval] +GO /** Remove AspNet Data **/