Those of you familiar with Perforce will know that Domain Authentication is achieved by means of a server side trigger which calls an executable to perform an ActiveDirectory validation of the user and password passed. Perforce has a tool for this which they provide free of charge, documented in their Knowledge Base (article 74). However the code is hard to read (predominantly because of the ActiveDirectory C++ API), and the command line is far more complicated than it need be.
The example below is an attempt at making this tool significantly easier to read and use,while using F# combined with log4net. There are 3 files to the solution:
- AssemblyInfo.fs
- auth.fs
- app.config
The result is a tool that only requires a domain and user name to be passed from the Perforce trigger to authenticate the user, and to log Perforce login activity to any number of destinations.
===================================================================
Using an 'AssemblyInfo' file in F# is just like in C# except maybe for the closing '()' which is a do-binding; MSDN has an article with the details. The only thing that is really different and important to know in F# is that file order matters, this means that in our example 'AssemblyInfo.fs' must come before 'auth.fs' in the '.fsproj' file, more details below.
#light
namespace AntekBaranski
open System.Reflection
open System.Runtime. CompilerServices
open System.Runtime.InteropServices
open System.Runtime.
open System.Runtime.InteropServices
[<assembly: AssemblyCompany("Company")>]
[<assembly: AssemblyProduct("auth")>]
[<assembly: AssemblyName("Name")>]
[<assembly: AssemblyCopyright("Copyright")>]
[<assembly: AssemblyTrademark("Trademark ™")>]
[<assembly: AssemblyDescription(" Description")>]
[<assembly: AssemblyProduct("auth")>]
[<assembly: AssemblyName("Name")>]
[<assembly: AssemblyCopyright("Copyright")>]
[<assembly: AssemblyTrademark("Trademark ™")>]
[<assembly: AssemblyDescription("
#if DEBUG
[<assembly: AssemblyConfiguration("Debug") >]
#else
[<assembly: AssemblyConfiguration(" Release")>]
#endif
[<assembly: AssemblyConfiguration("Debug")
#else
[<assembly: AssemblyConfiguration("
#endif
[<assembly: ComVisible(false)>]
[<assembly: AssemblyVersion("1.0.0.0")>]
[<assembly: AssemblyFileVersion("1.0.0.0") >]
[<assembly: AssemblyFileVersion("1.0.0.0")
// Instructs log4net to read its configuration from AppName.exe.config file
[<assembly: log4net.Config. XmlConfigurator(Watch = true)>]
()
===================================================================[<assembly: log4net.Config.
()
#light
namespace AntekBaranski
open System
open System.IO
open System.DirectoryServices. AccountManagement
open log4net
open System.IO
open System.DirectoryServices.
open log4net
module p4auth_ad =
// Initialise the log4net logger with the AppName in this case let logger = LogManager.GetLogger("p4auth_ad")
let ValidateCredentails (domain : string) (user : string) : int =
try
let pc = new PrincipalContext(ContextType. Domain, domain)
let passwd : string = System.Console.ReadLine()
if pc.ValidateCredentials(user, passwd)
then logger.DebugFormat("User authentication succesful for {0}", user)
0
else logger.WarnFormat("Unknown user or bad password for {0}", user)
1
with
| _ as ex
-> logger.Error("Network or other error occured", ex)
1
try
let pc = new PrincipalContext(ContextType.
let passwd : string = System.Console.ReadLine()
if pc.ValidateCredentials(user, passwd)
then logger.DebugFormat("User authentication succesful for {0}", user)
0
else logger.WarnFormat("Unknown user or bad password for {0}", user)
1
with
| _ as ex
-> logger.Error("Network or other error occured", ex)
1
[<EntryPoint>]
let Main args =
match args with
| [| domain; user |] ->
ValidateCredentails domain user
| _ ->
logger.Fatal("Usage: auth domain user")
1
let Main args =
match args with
| [| domain; user |] ->
ValidateCredentails domain user
| _ ->
logger.Fatal("Usage: auth domain user")
1
===================================================================
The app.config file is no different than with any other .NET language and because its using log4net, user authentication from Perforce can now be logged in any number of ways, in this case I choose to write to the EventLog.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config. Log4NetConfigurationSectionHan dler, log4net" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4. 0" />
</startup>
<log4net>
<appender name="DefaultAppender" type="log4net.Appender. EventLogAppender" >
<layout type="log4net.Layout. PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<root>
<priority value="ALL" />
<appender-ref ref="DefaultAppender" />
</root>
</log4net>
</configuration>
<configSections>
<section name="log4net" type="log4net.Config.
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.
</startup>
<log4net>
<appender name="DefaultAppender" type="log4net.Appender.
<layout type="log4net.Layout.
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<root>
<priority value="ALL" />
<appender-ref ref="DefaultAppender" />
</root>
</log4net>
</configuration>