How to implement a credentials validator for a generic provider using a script
Introduction
The Scripting generic provider feature allows the use of scripts for credentials validation for a specific Generic provider connection, as an alternative to an external DLL.
To enable the scripting credentials validation, select Scripting generic provider
from the Credentials validator dropdown list:
When the Scripting generic provider
is chosen, you can specify the desired script in Script name for credentials validation. Note that the script must be created in the Script library first. The login and error views now can use custom hosted forms or continue using built-in views as before.
The credentials validation script
Just like the way of implementing the external DLL, credentials validation script is a C# class that implements the IGenericCredentialsValidator
interface. This can be done by adding a new script from Script library with Generic provider
script type:
Example
Suppose we want to port the DummyGenericValidatorUsernamePassword example to using a script to validate credentials instead of using an external DLL. You need to follow these steps:
- Prepare a
Generic provider
script
Create a new Generic provider
script named DummyGenericValidatorUsernamePassword (as above picture), then use this example code:
public class DummyGenericValidatorUsernamePassword : IGenericCredentialsValidator
{
const string ConnectionString = "ConnectionString";
const string UserName = "username";
const string Password = "password";
public CredentialsValidationResult Validate(ControllerContext cc, IDictionary<string, string> input, IIdentifyLogWriter logWriter)
{
var validationResult = ValidateInput(input);
LookupProtocolConnection(cc, logWriter);
if (validationResult.ResultCode != CredentialsValidationResultCode.Success)
{
logWriter.WriteWarning("Validate the input failed. Error code: " + validationResult.ResultCode +
". ExternalErrorMessages: " + string.Join("\n-", validationResult.ExternalErrorMessages));
validationResult.ShowErrorViewWhenResultCodeIsNotSuccess = true;
return validationResult;
}
var connectionString = input[ConnectionString];
try
{
var userRepository = new DummyUserRepository(connectionString);
var username = input[UserName].Trim();
var user = userRepository.GetUserByName(username);
if (user == null)
{
logWriter.WriteInformation("User not found. Search username: " + username);
validationResult.ResultCode = CredentialsValidationResultCode.UnknownUserName;
}
else
{
logWriter.WriteInformation(string.Format(CultureInfo.InvariantCulture,
"User information: [UserName: {0}], [Password: {1}], [IsDisabled: {2}], [IsLocked: {3}]",
user.Username,
user.Password, user.IsDisabled, user.IsLocked));
var password = input[Password].Trim();
if (user.Password.Trim() != password)
{
validationResult.ExternalErrorMessages.Add("Password is invalid. Expected: " + user.Password);
validationResult.ResultCode = CredentialsValidationResultCode.IncorrectPassword;
}
else if (user.IsDisabled)
{
validationResult.ResultCode = CredentialsValidationResultCode.UserDisabled;
}
else if (user.IsLocked)
{
validationResult.ResultCode = CredentialsValidationResultCode.UserLocked;
}
else
{
validationResult.UserIdentity = user.Username;
validationResult.ResultCode = CredentialsValidationResultCode.Success;
}
}
}
catch (Exception ex)
{
logWriter.WriteError("An unexpected exception happends. Error message: " + ex.Message + ". StackTrace: " + ex.StackTrace);
validationResult.ResultCode = CredentialsValidationResultCode.UnknownError;
validationResult.ExternalErrorMessages.Add(ex.Message);
}
logWriter.WriteInformation("Validation with result code: " + validationResult.ResultCode + ". ExternalErrorMessages: " +
string.Join("\n-", validationResult.ExternalErrorMessages));
validationResult.ShowErrorViewWhenResultCodeIsNotSuccess = true;
return validationResult;
}
/// <summary>
/// It is an example how to access protocol connection id and entityId in external modules
/// </summary>
/// <param name="cc"></param>
/// <param name="logWriter"></param>
private void LookupProtocolConnection(ControllerContext cc, IIdentifyLogWriter logWriter)
{
const string temporaryContextKey = "ici_TemporaryProtocolContext";
var httpContext = cc.HttpContext;
var temporaryContext = (SessionLoginContext)httpContext.Items[temporaryContextKey];
var contextIdKey = temporaryContext.ContextIdKey;
string contextId = contextIdKey.ContextId;
Guid protocolConnectionId = contextIdKey.ProtocolConnectionId;
string protocolConnectionEntityId = contextIdKey.ProtocolConnectionEntityId;
logWriter.WriteInformation(
string.Format("DummyGenericValidatorUsernamePassword. ContextId = '{0}'. Protocol connection id = '{1}', entityId = '{2}'", contextId, protocolConnectionId, protocolConnectionEntityId));
}
private static CredentialsValidationResult ValidateInput(IDictionary<string, string> input)
{
CredentialsValidationResult validationResult = new CredentialsValidationResult();
if (!input.ContainsKey(ConnectionString))
{
validationResult.ExternalErrorMessages.Add("Missing connection string field");
validationResult.ResultCode = CredentialsValidationResultCode.MissingRequiredFields;
return validationResult;
}
if (!input.ContainsKey(UserName))
{
validationResult.ExternalErrorMessages.Add("Missing username field");
validationResult.ResultCode = CredentialsValidationResultCode.MissingRequiredFields;
return validationResult;
}
if (!input.ContainsKey(Password))
{
validationResult.ExternalErrorMessages.Add("Missing password field.");
validationResult.ResultCode = CredentialsValidationResultCode.MissingRequiredFields;
return validationResult;
}
validationResult.ResultCode = CredentialsValidationResultCode.Success;
return validationResult;
}
}
/// <summary>
/// Dummy user model
/// </summary>
public class DummyUser
{
public string Username { get; set; }
public string Password { get; set; }
public bool IsDisabled { get; set; }
public bool IsLocked { get; set; }
}
/// <summary>
/// A repository which returns users from a sample user store.
/// </summary>
public class DummyUserRepository
{
private readonly string connectionString;
public DummyUserRepository(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
throw new ArgumentNullException("connectionString");
this.connectionString = connectionString;
}
internal DummyUser GetUserByName(string username)
{
DummyUser user = null;
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
var command = new SqlCommand("SELECT [UserName], [Password], [IsLocked], [IsDisabled] FROM [dbo].[DummyUser] WHERE [UserName] = @UserName", connection);
command.Parameters.AddWithValue("@UserName", username);
var dataReader = command.ExecuteReader();
if (dataReader.Read())
{
user = new DummyUser
{
Username = username,
Password = dataReader.GetString(dataReader.GetOrdinal("Password")),
IsDisabled = dataReader.GetBoolean(dataReader.GetOrdinal("IsDisabled")),
IsLocked = dataReader.GetBoolean(dataReader.GetOrdinal("IsLocked"))
};
}
}
return user;
}
}
This example script requires some additional assembly references and namespaces as following:
- Use the script on the Generic provider connection
After having the correct DummyGenericValidatorUsernamePassword script, now you can use it on your Generic provider connection as image below.
- Provide needed settings
External database: This example assumes that you have the database and data as described in this document.
Key/value pairs to pass to the external provider: The credentials validation script needs some input values to perform it job. In this example, we need to provide ConnectionString string to the external database:
- Login view name and Error view name: You can use the DummyGenericUsernamePasswordLogin and DummyGenericUsernamePasswordError views available in the
Views\PlugIn
folder, or any Razor views by coping them into theViews\Shared
folder of the Runtime. You also can convert these views to hosted forms, use the database text resources for localization and use them instead.