Scripting generic provider
Introduction
The Scripting generic provider feature allows you to use scripts for credential validation with a specific Generic provider connection as an alternative to an external DLL. To use this feature, 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. For login and error views, you can now use hosted forms or continue using built-in views as before.
Credentials validation script
Similar to implementing an external DLL, the credentials validation script is a C# class that implements the IGenericCredentialsValidator
interface. To create this script, add a new script in the Script Library with the Generic Provider
script type:
Example
Suppose you want to port the DummyGenericValidatorUsernamePassword example to use a script for credential validation instead of an external DLL. Follow these steps:
- Prepare a
Generic provider
script
Create a new Generic provider
script named DummyGenericValidatorUsernamePassword (as shown in the picture above), and use the following 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 happened. 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 the following additional assembly references and namespaces:
- Use the script in the Generic provider connection
Once the DummyGenericValidatorUsernamePassword script is ready, you can apply it to your Generic Provider connection, as shown in the image below.
- Configure the required settings
External database: This example assumes you have the database and data set up as described in this document.
Key/value pairs to pass to the external provider: The credentials validation script requires input values to perform its job. In this example, you need to provide a
ConnectionString
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. Alternatively, you can use any Razor views by copying them into theViews\Shared
folder of the Runtime. For further customization, you can convert these views into hosted forms, use database text resources for localization, and use them instead.