using Newtonsoft.Json.Linq; using System; using System.Collections.Specialized; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace BioID.BWSWebApi.Demo { /// /// If you do not have access to a BWS instance yet, you can request a /// BWS trial instance here (requires login). /// With your account you can then create and manage BWS applications and secrets in the /// BWS Portal (requires login). /// class Program { #region BWS Client Configuration /// /// Use your provided endpoint here. /// You can find this information in our BWS Portal - Configuration. /// const string ENDPOINT = "https://bws.bioid.com/extension/"; /// /// You need to have created an app in our BWS Portal to get the identifier. /// You can find this information in our BWS Portal - Configuration. /// /// YOU NEED TO PUT YOUR INFO HERE! const string APP_IDENTIFIER = "your-BWS-appID"; /// /// You need to have created an app in our BWS portal to get the app secret. /// You can find this information in our BWS Portal - Configuration. /// /// YOU NEED TO PUT YOUR INFO HERE! const string APP_SECRET = "your-BWS-appSecret"; /// /// Use your provided storage here. /// You can find this information in our BWS Portal - Configuration. /// /// YOU NEED TO PUT YOUR INFO HERE! const string STORAGE = "bioid"; /// /// Use your provided partition here. /// You can find this information in our BWS Portal - Configuration. /// /// YOU NEED TO PUT YOUR INFO HERE! const int PARTITION = 0; /// /// This integer value is finally the ID of the class, which clearly defines a user within a Storage and Partition. /// /// /// This value is defined by yourself! /// static int classId = 1; #endregion #region Sample Images /// The data-URL using the data URI scheme - data:[][;base64], /// /// This is a fake image (printed image)! /// public static string dataUrlFakeImage1 = TestImages.DataUrlFakeImage1; /// /// This is a fake image (printed image)! /// public static string dataUrlFakeImage2 = TestImages.DataUrlFakeImage2; /// /// This image was recorded from a live person. /// public static string dataUrlLiveImage1 = TestImages.DtaUrlLiveImage1; /// /// This image was recorded from a live person. /// public static string dataUrlLiveImage2 = TestImages.DataUrlLiveImage2; /// /// This image contains the ID Photo. /// public static string dataUrlIdPhotoImage = TestImages.DataUrlIdPhotoImage; /// /// This image is in high resolution. /// static string dataUrlHighResImage = TestImages.DataUrlHighResImage; /// /// This image contains multiple faces. /// static string dataUrlMultipleFacesImage = TestImages.DataUrlMultipleFacesImage; /// /// This image contains no suitable face (only half of the face is displayed). /// public static string dataUrlNoSuitableFaceImage = TestImages.DataUrlNoSuitableFaceImage; #endregion /// /// The traits that shall be used for the biometric operation. /// public enum Trait { Face, Periocular }; /// /// Specifies the task the issued token shall be used for. /// public enum TokenFor { enroll, verify, identify, livenessdetection }; static async Task Main(string[] args) { var arguments = new Arguments(args); if (arguments["help"] != null || arguments["h"] != null) { Usage(); return; } try { string filePath = string.Empty; if (arguments["live1"] != null) { filePath = arguments["live1"]; dataUrlLiveImage1 = await ConvertToDataUrlImageAsync(filePath); } if (arguments["live2"] != null) { filePath = arguments["live2"]; dataUrlLiveImage2 = await ConvertToDataUrlImageAsync(filePath); } if (arguments["fake1"] != null) { filePath = arguments["fake1"]; dataUrlFakeImage1 = await ConvertToDataUrlImageAsync(filePath); } if (arguments["fake2"] != null) { filePath = arguments["fake2"]; dataUrlFakeImage2 = await ConvertToDataUrlImageAsync(filePath); } if (arguments["id"] != null) { filePath = arguments["id"]; dataUrlIdPhotoImage = await ConvertToDataUrlImageAsync(filePath); } if (arguments["highRes"] != null) { filePath = arguments["highRes"]; dataUrlHighResImage = await ConvertToDataUrlImageAsync(filePath); } if (arguments["noFace"] != null) { filePath = arguments["noFace"]; dataUrlNoSuitableFaceImage = await ConvertToDataUrlImageAsync(filePath); } } catch (Exception e) { Console.WriteLine("Could not parse/load arguments:"); Console.WriteLine(e.Message); Usage(); return; } string bwsToken; string bcid = STORAGE + "." + PARTITION + "." + classId; bool accepted, success, isLive = false; // Test PhotoVerify bool match = await PhotoVerifyAsync(4, dataUrlLiveImage1, dataUrlLiveImage2, dataUrlIdPhotoImage); // Test liveness detection on two images isLive = await LiveDetectionAsync(dataUrlLiveImage1, dataUrlLiveImage2); // Test QualityCheck string qualityCheckResult = await QualityCheckAsync(true, "ICAO", dataUrlHighResImage); // Test Enrollment Console.WriteLine("\n\nTest enrollment"); // Get token bwsToken = await TokenAsync(bcid, TokenFor.enroll); // Upload images accepted = await UploadAsync(bwsToken, dataUrlLiveImage1); accepted &= await UploadAsync(bwsToken, dataUrlLiveImage2); // Biometric task - Enroll if (accepted) { success = await EnrollAsync(bwsToken); } // Test Verification Console.WriteLine("\n\nTest verification"); // Get token bwsToken = await TokenAsync(bcid, TokenFor.verify); // Upload images accepted = await UploadAsync(bwsToken, dataUrlLiveImage1); accepted &= await UploadAsync(bwsToken, dataUrlLiveImage2); // Biometric task - Verify if (accepted) { success = await VerifyAsync(bwsToken); } // Test Identification Console.WriteLine("\n\nTest identification"); // Get token bwsToken = await TokenAsync(bcid, TokenFor.identify); // Upload images accepted = await UploadAsync(bwsToken, dataUrlLiveImage1); accepted &= await UploadAsync(bwsToken, dataUrlLiveImage2); // Biometric task - Identify if (accepted) { success = await IdentifyAsync(bwsToken); } // Test upload with multiple faces. Console.WriteLine("\n\nTest with multiple faces..."); // Get token bwsToken = await TokenAsync(bcid, TokenFor.livenessdetection); // Upload image accepted = await UploadAsync(bwsToken, dataUrlMultipleFacesImage); // Test upload with no suitable face. Console.WriteLine("\n\nTest with no suitable face..."); // Get token bwsToken = await TokenAsync(bcid, TokenFor.livenessdetection); // Upload image accepted = await UploadAsync(bwsToken, dataUrlNoSuitableFaceImage); // Test Liveness Detection with live images. Console.WriteLine("\n\nTest with live images..."); // Get token bwsToken = await TokenAsync(bcid, TokenFor.livenessdetection); // Upload images accepted = await UploadAsync(bwsToken, dataUrlLiveImage1); accepted &= await UploadAsync(bwsToken, dataUrlLiveImage2); // Biometric task - LivenessDetection if (accepted) { isLive = await LivenessDetectionAsync(bwsToken); } // Test Liveness Detection with fake images. Console.WriteLine("\n\nTest with fake images..."); // Get token bwsToken = await TokenAsync(bcid, TokenFor.livenessdetection); // Upload images accepted = await UploadAsync(bwsToken, dataUrlFakeImage1); accepted &= await UploadAsync(bwsToken, dataUrlFakeImage2); // Biometric task - LivenessDetection should fail! if (accepted) { isLive = await LivenessDetectionAsync(bwsToken); } // Fetches the result of the last biometric operation Console.WriteLine("\n\nTest fetch last result"); string result = await ResultAsync(bwsToken); // Find out whether a user is already enrolled for a specific trait. Console.WriteLine("\nTest is enrolled"); bool isEnrolled = await IsEnrolledAsync(bcid, Trait.Face); // Deletes all biometric data associated with a Biometric Class ID(bcid)from the system. Console.WriteLine("\nTest delete class"); bool isDeleted = await DeleteClassAsync(bcid); // Find out whether a user is already enrolled for a specific trait. Console.WriteLine("\nTest is enrolled"); isEnrolled = await IsEnrolledAsync(bcid, Trait.Face); // Requests status information from a BWS installation Console.WriteLine("\nTest get BWS status"); string status = await BWSStatusAsync(); Console.WriteLine("Press any key to stop..."); Console.ReadKey(); } #region Biometric tasks without associated user (BCID) /// /// Performs a liveness detection on the uploaded samples to verify whether they are recorded from a live person. /// Then performs a one-to-one comparison with the ID photo submitted in /// order to verify whether the live images and ID photo belong to the same person. /// /// The desired accuracy level /// The first image recorded from a live person. /// The second image recorded form a live person. /// The ID photo of the recorded live person. /// The live images match the ID photo or not with regard to the applied accuracy level. private static async Task PhotoVerifyAsync(int accuracy, string dataUrlLiveImage1, string dataUrlLiveImage2, string dataUrlIdPhoto) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{APP_IDENTIFIER}:{APP_SECRET}"))); string mediaType, images; // using the application/json media type mediaType = "application/json"; images = $@"{{""liveimage1"":""{dataUrlLiveImage1}""" + $@",""liveimage2"":""{dataUrlLiveImage2}""" + $@",""idphoto"":""{dataUrlIdPhoto}""}}"; // or using application/x-www-form-urlencoded /* mediaType = "application/x-www-form-urlencoded"; images = string.Format("liveimage1={0}&liveimage2={1}&idphoto={2}", HttpUtility.UrlEncode(dataUrlLiveImage1), HttpUtility.UrlEncode(dataUrlLiveImage2), HttpUtility.UrlEncode(dataUrlIdPhoto)); */ using (var content = new StringContent(images, Encoding.ASCII, mediaType)) using (var response = await client.PostAsync(ENDPOINT + $"photoverify?accuracy={accuracy}", content)) { Console.Write("PhotoVerify Response... "); string result = await response.Content.ReadAsStringAsync(); if (response.StatusCode == HttpStatusCode.OK) { if (bool.TryParse(result, out var match)) { Console.WriteLine(match); return match; } } Console.WriteLine(response.StatusCode.ToString()); Console.WriteLine(result); return false; } } } /// /// Performs a face liveness detection on two images. /// /// The first image recorded from a live person. /// The second image recorded form a live person. /// Is recorded from a live person or not. private static async Task LiveDetectionAsync(string dataUrlLiveImage1, string dataUrlLiveImage2) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{APP_IDENTIFIER}:{APP_SECRET}"))); string mediaType, images; // using the application/json media type mediaType = "application/json"; images = $@"{{""liveimage1"":""{dataUrlLiveImage1}""" + $@",""liveimage2"":""{dataUrlLiveImage2}""}}"; // or using application/x-www-form-urlencoded /* mediaType = "application/x-www-form-urlencoded"; images = string.Format("liveimage1={0}&liveimage2={1}", HttpUtility.UrlEncode(dataUrlLiveImage1), HttpUtility.UrlEncode(dataUrlLiveImage2)); */ using (var content = new StringContent(images, Encoding.UTF8, mediaType)) using (var response = await client.PostAsync(ENDPOINT + "livedetection", content)) { Console.Write("LiveDetection Response... "); string result = await response.Content.ReadAsStringAsync(); if (response.StatusCode == HttpStatusCode.OK) { if (bool.TryParse(result, out var isLive)) { Console.WriteLine(isLive); return isLive; } } Console.WriteLine(response.StatusCode.ToString()); Console.WriteLine(result); return false; } } } /// /// Performs a rigorous quality check according to ICAO (International Civil Aviation Organization) requirements /// for machine readable travel documents (MRTDs). /// /// Full document mode otherwise token data mode is used. /// Document-issuer specific settings. /// The image for the quality check. /// /// Returns a QualityCheckResult object, which informs about the Success of the check and /// lists Errors that occurred during the check. /// private static async Task QualityCheckAsync(bool full, string issuer, string dataUrlImage) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{APP_IDENTIFIER}:{APP_SECRET}"))); using (var content = new StringContent(dataUrlImage)) using (var response = await client.PostAsync(ENDPOINT + $"qualitycheck?full={full}&issuer={issuer}", content)) { Console.Write("QualityCheck Response... "); if (response.StatusCode == HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(result); bool success = (bool)json["Success"]; var errors = json["Errors"]; var eyeCenters = json["EyeCenters"]; string base64ProcessSample = (string)json["ProcessedSample"]; Console.WriteLine(success); return json.ToString(); } Console.WriteLine(response.StatusCode.ToString()); return string.Empty; } } } #endregion #region Basic biometric tasks for Enroll, Verify, Identify associated with a user (BCID) and pure Liveness Detection /// /// Get a BWS token to be used for authorization. /// /// The Biometric Class ID (BCID) of the person. /// The task the issued token shall be used for. /// A string containing the issued BWS token. private static async Task TokenAsync(string bcid, TokenFor forTask = TokenFor.verify) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{APP_IDENTIFIER}:{APP_SECRET}"))); using (var response = await client.GetAsync(ENDPOINT + $"token?id={APP_IDENTIFIER}&bcid={bcid}&task={forTask}")) { Console.Write("Get BWS token for " + forTask + "... "); if (response.StatusCode == HttpStatusCode.OK) { Console.WriteLine("succeeded"); return await response.Content.ReadAsStringAsync(); } Console.WriteLine(response.StatusCode.ToString()); return string.Empty; } } } /// /// Asynchronous upload for the images that are associated with a token and can later be used /// for various biometric operations. /// /// The previously issued BWS token. /// The image for the upload. /// The image has passed the quality-check or not. private static async Task UploadAsync(string bwsToken, string dataUrlImage) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bwsToken); using (var content = new StringContent(dataUrlImage)) using (var response = await client.PostAsync(ENDPOINT + $"upload", content)) { Console.Write("Uploading image... "); if (response.StatusCode == HttpStatusCode.OK) { Console.WriteLine("succeeded"); string result = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(result); bool accepted = (bool)json["Accepted"]; string error = (string)json["Error"]; if (!string.IsNullOrEmpty(error)) { Console.WriteLine(error); } var warnings = json["Warnings"]; return accepted; } Console.WriteLine(response.StatusCode.ToString()); return false; } } } /// /// Performs a biometric enrollment (or training) of the user with the uploaded images /// that are associated with the token provided for authorization. /// /// The previously issued BWS token. /// The enrollment succeeded or not. private static async Task EnrollAsync(string bwsToken) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bwsToken); using (var response = await client.GetAsync(ENDPOINT + $"enroll")) { Console.Write("Enrollment response... "); if (response.StatusCode == HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(result); bool success = (bool)json["Success"]; Console.Write("Success: " + success); if (!success) { // read out the error string error = (string)json["Error"]; Console.WriteLine(" - Error: " + error); } return success; } Console.WriteLine(response.StatusCode.ToString()); return false; } } } /// /// Performs a one-to-one comparison of the uploaded samples with a stored biometric template in /// order to verify whether the individual is the person he or she claims to be. /// /// The previously issued BWS token. /// The verification succeeded or not. private static async Task VerifyAsync(string bwsToken) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bwsToken); using (var response = await client.GetAsync(ENDPOINT + $"verify")) { Console.Write("Verification response... "); if (response.StatusCode == HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(result); bool success = (bool)json["Success"]; Console.Write("Success: " + success); if (!success) { // read out the error string error = (string)json["Error"]; Console.WriteLine(" - Error: " + error); } return success; } Console.WriteLine(response.StatusCode.ToString()); return false; } } } /// /// Performs a one-to-many comparison of the uploaded samples with stored biometric templates in order to /// identify the individuals that are most likely represented by the given samples. /// /// The previously issued BWS token. /// The identification could be performed or not. private static async Task IdentifyAsync(string bwsToken) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bwsToken); using (var response = await client.GetAsync(ENDPOINT + $"identify")) { Console.Write("Identification response... "); if (response.StatusCode == HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(result); bool success = (bool)json["Success"]; Console.Write("Success: " + success); if (!success) { // read out the error string error = (string)json["Error"]; Console.WriteLine(" - Error: " + error); } return success; } Console.WriteLine(response.StatusCode.ToString()); return false; } } } /// /// Performs a liveness detection on the previously uploaded samples that are associated /// with the token provided for authorization. /// /// The previously issued BWS token. /// The liveness detection succeeded or not. private static async Task LivenessDetectionAsync(string bwsToken) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bwsToken); using (var response = await client.GetAsync(ENDPOINT + $"livenessdetection")) { Console.Write("LivenessDetection response... "); if (response.StatusCode == HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(result); bool success = (bool)json["Success"]; Console.Write("Success: " + success); if (!success) { // read out the error string error = (string)json["Error"]; Console.WriteLine(" - Error: " + error); } return success; } Console.WriteLine(response.StatusCode.ToString()); return false; } } } #endregion #region User management (BCID) and BWS status and results of a biometric operation /// /// Find out whether a user is already enrolled for a specific trait. /// /// The Biometric Class ID (BCID) of the person for which to find out whether he or she is already enrolled or not. /// The biometric trait (e.g. face, or any other of the supported traits) for which to look for a template belonging to the specified person. /// A template for the specified BCID and trait is available or not. private static async Task IsEnrolledAsync(string bcid, Trait trait) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{APP_IDENTIFIER}:{APP_SECRET}"))); using (var response = await client.GetAsync(ENDPOINT + $"isenrolled?bcid={bcid}&trait={trait}")) { Console.Write("IsEnrolled response... "); if (response.StatusCode == HttpStatusCode.OK) { Console.WriteLine(true); return true; } Console.WriteLine(response.StatusCode.ToString()); return false; } } } /// /// Deletes all biometric data associated with a Biometric Class ID from the system. /// /// The Biometric Class ID (BCID) of the class that shall get deleted. /// The class has been deleted or not. private static async Task DeleteClassAsync(string bcid) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{APP_IDENTIFIER}:{APP_SECRET}"))); using (var response = await client.DeleteAsync(ENDPOINT + $"deleteclass?bcid={bcid}")) { Console.WriteLine("DeleteClass response... " + response.IsSuccessStatusCode); return response.IsSuccessStatusCode; } } } /// /// Fetches the result of the last biometric operation /// (i.e. verification, identification, enrollment or liveness detection). /// /// The BWS web token that was used to perform the biometric task. /// Returns an OperationResult object describing the result of the last biometric operation. private static async Task ResultAsync(string bwsToken) { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{APP_IDENTIFIER}:{APP_SECRET}"))); using (var response = await client.GetAsync(ENDPOINT + $"result?access_token={bwsToken}")) { Console.Write("Result response... "); if (response.StatusCode == HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(result); bool success = (bool)json["Success"]; string action = (string)json["Action"]; string bcid = (string)json["BCID"]; // For identify we receive the matches if (action == TokenFor.identify.ToString()) { // we can see the matches var matches = json["Matches"]; } if (!success) { // read out the error string error = (string)json["Error"]; Console.WriteLine(error); } Console.WriteLine(json.ToString()); return json.ToString(); } Console.WriteLine(response.StatusCode.ToString()); return string.Empty; } } } /// /// Requests status information from a BWS installation. /// /// /// Returns a StatusResult object, containing information about the running BWS installation. /// private static async Task BWSStatusAsync() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{APP_IDENTIFIER}:{APP_SECRET}"))); using (var response = await client.GetAsync(ENDPOINT + $"status")) { Console.Write("BWSStatus response... "); if (response.StatusCode == HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); var json = JObject.Parse(result); // read out status information string version = (string)json["Version"]; string label = (string)json["Label"]; var features = json["Features"]; Console.WriteLine(json.ToString()); return json.ToString(); } Console.WriteLine(response.StatusCode.ToString()); return string.Empty; } } } #endregion #region Helper functions /// /// The usage description for the command line tool. /// private static void Usage() { Console.Error.Write(@" TestBWSWebApi is a simple BWS command line tool that executes all available commands at the specified BioID Web Service. Default images for live recorded images and fake images are available. You can specify other images for live and fake images per command line. Syntax: TestBWSWebApi /help | /h TestBWSWebApi [/live1:] [/live2:] | [/fake1:] [/fake2:] | [/id:] | [/highRes:] | [/noFace:] "); } /// /// Converts an image to a data url presentation. /// /// The file path of the image. /// The image in data url format. private static async Task ConvertToDataUrlImageAsync(string imageFilePath) { byte[] image = await File.ReadAllBytesAsync(imageFilePath); string imageType = Path.GetExtension(imageFilePath).ToLower().Replace(".", ""); return $"data:image/{imageType};base64," + Convert.ToBase64String(image); } #endregion } /// /// Initializes a new instance of the class to parse command lines. /// /// The command line to parse. public class Arguments { /// /// List of prefixed arguments /// private StringDictionary prefixedParams = new StringDictionary(); /// /// Initializes a new instance of the class to parse command lines. /// /// The command line to parse. public Arguments(string[] args) { Regex spliter = new Regex(@"^([/-]|--){1}(?\w+)([:=])?(?.+)?$", RegexOptions.IgnoreCase | RegexOptions.Compiled); char[] trimChars = { '"', '\'' }; Match part; foreach (string arg in args) { part = spliter.Match(arg); if (part.Success) prefixedParams[part.Groups["name"].Value] = part.Groups["value"].Value.Trim(trimChars); } } /// /// Retrieves the parameter with the specified name. /// /// /// The name of the parameter. The name is case insensitive. /// /// /// The parameter or null if it cannot be found. /// public string this[string name] { get { return prefixedParams[name]; } } } }