//******************************************************************************************************
// SecurityPortal.xaml.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 12/14/2010 - Mehulbhai P Thakkar
// Generated original version of source code.
// 02/16/2011 - J. Ritchie Carroll
// Added automatic password expiration handling and modified screen to accomodate default buttons.
// 09/22/2011 - J. Ritchie Carroll
// Excluded class from Mono deployments since much of WPF is not currently available.
// 12/20/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Security.Principal;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using GSF.Configuration;
using GSF.Identity;
using GSF.Security;
namespace GSF.Windows
{
#if !MONO
#region [ Enumerations ]
///
/// Enumerable list of portlets to be displayed on the screen.
///
public enum DisplayType
{
///
/// Login screen.
///
Login,
///
/// Access Denied screen.
///
AccessDenied,
///
/// Change Password screen.
///
ChangePassword
}
#endregion
///
/// Represents a WPF window used to request user credentials and other security related information.
///
public partial class SecurityPortal : Window
{
#region [ Members ]
// Fields
private DisplayType m_displayType;
private bool m_providerFailure;
#endregion
#region [ Constructors ]
///
/// Initializes a new WPF window.
///
/// Type of the message received from security API which is used to decide controls to be displayed on the screen.
public SecurityPortal(DisplayType displayType)
{
InitializeComponent();
m_displayType = displayType;
Closed += Window_Closed;
MouseDown += Window_MouseDown;
ButtonLogin.Click += ButtonLogin_Click;
ButtonExit.Click += ButtonExit_Click;
ButtonOK.Click += ButtonOK_Click;
ButtonChange.Click += ButtonChange_Click;
ButtonChangePasswordLink.Click += ButtonChangePasswordLink_Click;
ButtonForgotPasswordLink.Click += ButtonForgotPasswordLink_Click;
ButtonLoginLink.Click += ButtonLoginLink_Click;
TextBoxUserName.TextChanged += TextBox_TextChanged;
TextBoxPassword.PasswordChanged += PasswordBox_PasswordChanged;
TextBoxChangePasswordUserName.TextChanged += TextBox_TextChanged;
TextBoxOldPassword.PasswordChanged += PasswordBox_PasswordChanged;
TextBoxNewPassword.PasswordChanged += PasswordBox_PasswordChanged;
TextBoxConfirmPassword.PasswordChanged += PasswordBox_PasswordChanged;
TextBoxUserName.GotFocus += TextBox_GotFocus;
TextBoxPassword.GotFocus += TextBox_GotFocus;
TextBoxChangePasswordUserName.GotFocus += TextBox_GotFocus;
TextBoxOldPassword.GotFocus += TextBox_GotFocus;
TextBoxNewPassword.GotFocus += TextBox_GotFocus;
TextBoxConfirmPassword.GotFocus += TextBox_GotFocus;
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[SecurityProviderBase.DefaultSettingsCategory];
string setting = settings["ApplicationName"].Value;
if (!string.IsNullOrWhiteSpace(setting))
{
TextBlockApplicationLogin.Text = setting + " :: Login";
TextBlockAccessDenied.Text = setting + " :: Access Denied";
TextBlockChangePassword.Text = setting + " :: Change Password";
}
// Load last user login ID setting
settings.Add("LastLoginID", Thread.CurrentPrincipal.Identity.Name, "Last user login ID", false, SettingScope.User);
setting = settings["LastLoginID"].Value;
if (string.IsNullOrWhiteSpace(setting))
setting = Thread.CurrentPrincipal.Identity.Name;
TextBoxUserName.Text = setting;
// Inititialize screen
ClearErrorMessage();
ManageScreenVisualization();
// Open this window on top of all other windows
Topmost = true;
}
#endregion
#region [ Properties ]
///
/// Gets the security principal used for role-based authorization.
///
public SecurityPrincipal SecurityPrincipal
{
get;
private set;
}
///
/// Gets or sets flag that indicates if there was a failure during provider initialization.
///
public bool ProviderFailure
{
get
{
return m_providerFailure;
}
set
{
m_providerFailure = value;
TextBlockAccessDeniedMessage.Visibility = m_providerFailure ? Visibility.Collapsed : Visibility.Visible;
}
}
// Handles dialog exiting
private bool ExitSuccess
{
set
{
if (!string.IsNullOrWhiteSpace(TextBoxUserName.Text))
{
ConfigurationFile.Current.Settings[SecurityProviderBase.DefaultSettingsCategory]["LastLoginID"].Value = TextBoxUserName.Text;
ConfigurationFile.Current.Save();
}
if (DialogResult == null)
DialogResult = value;
}
}
#endregion
#region [ Methods ]
///
/// Displays requested screen section based on received request.
///
private void ManageScreenVisualization()
{
// Initially hide everything on all screens
LoginSection.Visibility = Visibility.Collapsed;
AccessDeniedSection.Visibility = Visibility.Collapsed;
ChangePasswordSection.Visibility = Visibility.Collapsed;
TextBlockApplicationLogin.Visibility = Visibility.Collapsed;
TextBlockAccessDenied.Visibility = Visibility.Collapsed;
TextBlockChangePassword.Visibility = Visibility.Collapsed;
// Reset all default buttons
ButtonLogin.IsDefault = false;
ButtonOK.IsDefault = false;
ButtonChange.IsDefault = false;
if (m_displayType == DisplayType.Login)
{
TextBoxPassword.Password = "";
ButtonLogin.IsDefault = true;
TextBlockApplicationLogin.Visibility = Visibility.Visible;
LoginSection.Visibility = Visibility.Visible;
if (string.IsNullOrWhiteSpace(TextBoxUserName.Text))
TextBoxUserName.Focus();
else
TextBoxPassword.Focus();
}
else if (m_displayType == DisplayType.AccessDenied)
{
ButtonOK.IsDefault = true;
TextBlockAccessDenied.Visibility = Visibility.Visible;
AccessDeniedSection.Visibility = Visibility.Visible;
}
else if (m_displayType == DisplayType.ChangePassword)
{
TextBoxOldPassword.Password = "";
TextBoxNewPassword.Password = "";
TextBoxConfirmPassword.Password = "";
ButtonChange.IsDefault = true;
TextBlockChangePassword.Visibility = Visibility.Visible;
ChangePasswordSection.Visibility = Visibility.Visible;
if (string.IsNullOrWhiteSpace(TextBoxChangePasswordUserName.Text))
TextBoxChangePasswordUserName.Focus();
else
TextBoxOldPassword.Focus();
}
}
private bool ShowFailureReason(ISecurityProvider provider)
{
ClearErrorMessage();
if (!provider.UserData.IsDefined)
// No such account.
DisplayErrorMessage("Account does not exist.");
else if (provider.UserData.IsDisabled)
// Account is disabled.
DisplayErrorMessage("Account is currently disabled.");
else if (provider.UserData.IsLockedOut)
// Account is locked.
DisplayErrorMessage("Account is currently locked.");
else if (provider.UserData.Roles.Count == 0)
// No roles are assigned
DisplayErrorMessage("Account has not been assigned any roles and therefore has no rights. Contact your administrator.");
else
return false;
return true;
}
///
/// Displays error message.
///
/// Error message to display.
public void DisplayErrorMessage(string message)
{
if ((object)TextBlockGlobalMessage != null)
{
if (string.IsNullOrWhiteSpace(message))
{
TextBlockGlobalMessage.Text = "";
TextBlockGlobalMessage.Visibility = Visibility.Collapsed;
}
else
{
TextBlockGlobalMessage.Text = message;
TextBlockGlobalMessage.Visibility = Visibility.Visible;
}
}
}
///
/// Clears error message.
///
public void ClearErrorMessage()
{
DisplayErrorMessage(null);
}
private bool TryImpersonate(string loginID, string password, out WindowsImpersonationContext impersonationContext)
{
string domain;
string username;
string[] splitLoginID;
try
{
splitLoginID = loginID.Split('\\');
if (splitLoginID.Length == 2)
{
domain = splitLoginID[0];
username = splitLoginID[1];
impersonationContext = UserInfo.ImpersonateUser(domain, username, password);
return true;
}
}
catch (InvalidOperationException)
{
}
impersonationContext = null;
return false;
}
#region [ Event Handlers ]
///
/// Handles window closed.
///
/// Source of this event.
/// Arguments of this event.
private void Window_Closed(object sender, EventArgs e)
{
ExitSuccess = false;
}
///
/// Handles mouse down event anywhere on the screen so user can click and drag this window around.
///
///
///
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
DragMove();
}
///
/// Logins the user.
///
/// Source of this event.
/// Arguments of this event.
private void ButtonLogin_Click(object sender, RoutedEventArgs e)
{
UserInfo userInfo;
WindowsImpersonationContext impersonationContext = null;
ISecurityProvider securityProvider;
try
{
// Determine whether we need to try impersonating the user
userInfo = new UserInfo(TextBoxUserName.Text);
// If the application is unable to access the domain, possibly because the local user
// running the application does not have access to domain objects, it's possible that
// the user logging in does have access to the domain. So we attempt to impersonate the
// user logging in to allow authentication to proceed
if (!userInfo.DomainRespondsForUser && TryImpersonate(userInfo.LoginID, TextBoxPassword.Password, out impersonationContext))
{
try
{
// Working around a known issue - DirectorySearcher will often throw
// an exception the first time it is used after impersonating another
// user so we get that out of the way here
userInfo.Initialize();
}
catch (InitializationException)
{
// Exception is expected so we ignore it
}
}
// Initialize the security provider
securityProvider = SecurityProviderCache.CreateProvider(TextBoxUserName.Text);
securityProvider.SecurePassword = TextBoxPassword.SecurePassword;
// Attempt to authenticate user
if (securityProvider.Authenticate())
{
// Setup security principal for subsequent uses
SecurityIdentity securityIdentity = new SecurityIdentity(securityProvider);
SecurityPrincipal = new SecurityPrincipal(securityIdentity);
ClearErrorMessage();
ExitSuccess = true;
}
else
{
// Verify their password hasn't expired
if (securityProvider.UserData.IsDefined && securityProvider.UserData.PasswordChangeDateTime <= DateTime.UtcNow)
{
// Display password expired message
DisplayErrorMessage(string.Format("Your password has expired. {0} You must change your password to continue.", securityProvider.AuthenticationFailureReason));
m_displayType = DisplayType.ChangePassword;
ManageScreenVisualization();
TextBoxPassword.Password = "";
}
else
{
// Display login failure message
DisplayErrorMessage("The username or password is invalid. " + securityProvider.AuthenticationFailureReason);
if (string.IsNullOrWhiteSpace(TextBoxUserName.Text))
TextBoxUserName.Focus();
else
TextBoxPassword.Focus();
}
}
}
catch (Exception ex)
{
DisplayErrorMessage("Login failed: " + ex.Message);
if (string.IsNullOrWhiteSpace(TextBoxUserName.Text))
TextBoxUserName.Focus();
else
TextBoxPassword.Focus();
}
finally
{
if ((object)impersonationContext != null)
{
impersonationContext.Undo();
impersonationContext.Dispose();
}
}
}
///
/// Attempts to change user's password.
///
/// Source of this event.
/// Arguments of this event.
private void ButtonChange_Click(object sender, RoutedEventArgs e)
{
try
{
// Check if old and new password are different
if (TextBoxOldPassword.Password == TextBoxNewPassword.Password)
throw new Exception("New password cannot be same as old password.");
// Check is new password and confirm password are same
if (TextBoxNewPassword.Password != TextBoxConfirmPassword.Password)
throw new Exception("New password and confirm password should be same.");
ISecurityProvider securityProvider = SecurityProviderCache.CreateProvider(TextBoxChangePasswordUserName.Text);
securityProvider.SecurePassword = TextBoxNewPassword.SecurePassword;
if (securityProvider.CanChangePassword)
{
// Attempt to change password
if (securityProvider.ChangePassword(TextBoxOldPassword.Password, TextBoxNewPassword.Password) &&
securityProvider.Authenticate())
{
// Password changed and authenticated successfully
DisplayErrorMessage("Password changed successfully.");
// Setup security principal for subsequent uses
SecurityIdentity securityIdentity = new SecurityIdentity(securityProvider);
SecurityPrincipal = new SecurityPrincipal(securityIdentity);
ClearErrorMessage();
ExitSuccess = true;
}
else
{
// Show why password change failed
if (!ShowFailureReason(securityProvider))
{
if (!securityProvider.IsUserAuthenticated)
DisplayErrorMessage("Authentication was not successful.");
else
DisplayErrorMessage("Password change was not successful.");
if (string.IsNullOrWhiteSpace(TextBoxChangePasswordUserName.Text))
TextBoxChangePasswordUserName.Focus();
else
TextBoxOldPassword.Focus();
}
}
}
else
DisplayErrorMessage("Account does not support password change.");
}
catch (Exception ex)
{
DisplayErrorMessage("Change password failed: " + ex.Message);
TextBoxOldPassword.Focus();
}
}
///
/// Exits window.
///
/// Source of this event.
/// Arguments of this event.
private void ButtonExit_Click(object sender, RoutedEventArgs e)
{
ExitSuccess = false;
}
///
/// Closes window.
///
/// Source of this event.
/// Arguments of this event.
private void ButtonOK_Click(object sender, RoutedEventArgs e)
{
if (m_providerFailure)
{
// In case of provider failure, all we can do is exit with a failure code
ExitSuccess = false;
}
else
{
// If user chooses, they can try a new set of credentials
m_displayType = DisplayType.Login;
ManageScreenVisualization();
}
}
///
/// Displays help for the user on how to reset password.
///
/// Source of this event.
/// Arguments of this event.
private void ButtonForgotPasswordLink_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Please contact application administrator to reset your password.", "Forgot Password");
}
///
/// Navigates users to Change Password screen.
///
/// Source of this event.
/// Arguments of this event.
private void ButtonChangePasswordLink_Click(object sender, RoutedEventArgs e)
{
ClearErrorMessage();
m_displayType = DisplayType.ChangePassword;
ManageScreenVisualization();
}
///
/// Navigates users back to Login screen.
///
/// Source of this event.
/// Arguments of this event.
private void ButtonLoginLink_Click(object sender, RoutedEventArgs e)
{
ClearErrorMessage();
m_displayType = DisplayType.Login;
ManageScreenVisualization();
}
///
/// Keeps the user names on the separate sections synchronized and clears error messages when text changes.
///
/// Source of this event.
/// Arguments of this event.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (LoginSection.Visibility == Visibility.Visible && ReferenceEquals(e.Source, TextBoxUserName))
TextBoxChangePasswordUserName.Text = TextBoxUserName.Text;
else if (ChangePasswordSection.Visibility == Visibility.Visible && ReferenceEquals(e.Source, TextBoxChangePasswordUserName))
TextBoxUserName.Text = TextBoxChangePasswordUserName.Text;
ClearErrorMessage();
}
///
/// Clears error messages when text changes.
///
/// Source of this event.
/// Arguments of this event.
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
ClearErrorMessage();
}
///
/// We make sure all text boxes "select-all" when they receive focus.
///
/// Source of this event.
/// Arguments of this event.
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
if ((object)sender != null)
{
TextBox box = sender as TextBox;
if ((object)box != null)
{
box.SelectAll();
}
else
{
PasswordBox passwordBox = sender as PasswordBox;
if ((object)passwordBox != null)
passwordBox.SelectAll();
}
}
}
#endregion
#endregion
}
#endif
}