QR2FAWindow.xaml.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. using InABox.Clients;
  2. using InABox.Core;
  3. using InABox.Wpf;
  4. using Microsoft.Exchange.WebServices.Data;
  5. using Syncfusion.UI.Xaml.Controls.Barcode;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Text;
  10. using System.Threading.Tasks;
  11. using System.Web;
  12. using System.Windows;
  13. using System.Windows.Controls;
  14. using System.Windows.Data;
  15. using System.Windows.Documents;
  16. using System.Windows.Input;
  17. using System.Windows.Media;
  18. using System.Windows.Media.Imaging;
  19. using System.Windows.Shapes;
  20. namespace PRSDesktop.Panels.Users
  21. {
  22. /// <summary>
  23. /// Interaction logic for QR2FAWindow.xaml
  24. /// </summary>
  25. public partial class QR2FAWindow : ThemableWindow
  26. {
  27. private User User;
  28. public QR2FAWindow(User user)
  29. {
  30. User = user;
  31. user.AuthenticatorToken ??= User.GenerateAuthenticationToken();
  32. InitializeComponent();
  33. Barcode.QuietZone.All = 0;
  34. SetBarcodeText(user.UserID, user.AuthenticatorToken);
  35. Reset.IsEnabled = Security.CanEdit<User>();
  36. }
  37. public void SetBarcodeText(string userID, byte[] key)
  38. {
  39. var secret = Base32Encoding.ToString(key).Trim('=');
  40. var info = new Client<CompanyInformation>().Load().FirstOrDefault();
  41. if (info == null)
  42. info = new CompanyInformation();
  43. SetupKey.Text = secret;
  44. Barcode.Text = $"otpauth://totp/PRS-{Uri.EscapeDataString(userID)}?secret={secret}&issuer=PRS%20-%20{Uri.EscapeDataString(info.CompanyName)}";
  45. }
  46. private void OK_Click(object sender, RoutedEventArgs e)
  47. {
  48. DialogResult = true;
  49. Close();
  50. }
  51. private void Reset_Click(object sender, RoutedEventArgs e)
  52. {
  53. if(MessageBox.Show("Doing this will invalidate the authentication of this user, preventing them from being able to log in until they have rescanned this code. Do you wish to proceed?", "Continue?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
  54. {
  55. User.AuthenticatorToken = User.GenerateAuthenticationToken();
  56. SetBarcodeText(User.UserID, User.AuthenticatorToken);
  57. }
  58. }
  59. }
  60. // Code taken from https://stackoverflow.com/questions/641361/base32-decoding
  61. public class Base32Encoding
  62. {
  63. public static byte[] ToBytes(string input)
  64. {
  65. if (string.IsNullOrEmpty(input))
  66. {
  67. throw new ArgumentNullException("input");
  68. }
  69. input = input.TrimEnd('='); //remove padding characters
  70. int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
  71. byte[] returnArray = new byte[byteCount];
  72. byte curByte = 0, bitsRemaining = 8;
  73. int mask = 0, arrayIndex = 0;
  74. foreach (char c in input)
  75. {
  76. int cValue = CharToValue(c);
  77. if (bitsRemaining > 5)
  78. {
  79. mask = cValue << (bitsRemaining - 5);
  80. curByte = (byte)(curByte | mask);
  81. bitsRemaining -= 5;
  82. }
  83. else
  84. {
  85. mask = cValue >> (5 - bitsRemaining);
  86. curByte = (byte)(curByte | mask);
  87. returnArray[arrayIndex++] = curByte;
  88. curByte = (byte)(cValue << (3 + bitsRemaining));
  89. bitsRemaining += 3;
  90. }
  91. }
  92. //if we didn't end with a full byte
  93. if (arrayIndex != byteCount)
  94. {
  95. returnArray[arrayIndex] = curByte;
  96. }
  97. return returnArray;
  98. }
  99. public static string ToString(byte[] input)
  100. {
  101. if (input == null || input.Length == 0)
  102. {
  103. throw new ArgumentNullException("input");
  104. }
  105. int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
  106. char[] returnArray = new char[charCount];
  107. byte nextChar = 0, bitsRemaining = 5;
  108. int arrayIndex = 0;
  109. foreach (byte b in input)
  110. {
  111. nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
  112. returnArray[arrayIndex++] = ValueToChar(nextChar);
  113. if (bitsRemaining < 4)
  114. {
  115. nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
  116. returnArray[arrayIndex++] = ValueToChar(nextChar);
  117. bitsRemaining += 5;
  118. }
  119. bitsRemaining -= 3;
  120. nextChar = (byte)((b << bitsRemaining) & 31);
  121. }
  122. //if we didn't end with a full char
  123. if (arrayIndex != charCount)
  124. {
  125. returnArray[arrayIndex++] = ValueToChar(nextChar);
  126. while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
  127. }
  128. return new string(returnArray);
  129. }
  130. private static int CharToValue(char c)
  131. {
  132. int value = (int)c;
  133. //65-90 == uppercase letters
  134. if (value < 91 && value > 64)
  135. {
  136. return value - 65;
  137. }
  138. //50-55 == numbers 2-7
  139. if (value < 56 && value > 49)
  140. {
  141. return value - 24;
  142. }
  143. //97-122 == lowercase letters
  144. if (value < 123 && value > 96)
  145. {
  146. return value - 97;
  147. }
  148. throw new ArgumentException("Character is not a Base32 character.", "c");
  149. }
  150. private static char ValueToChar(byte b)
  151. {
  152. if (b < 26)
  153. {
  154. return (char)(b + 65);
  155. }
  156. if (b < 32)
  157. {
  158. return (char)(b + 24);
  159. }
  160. throw new ArgumentException("Byte is not a value Base32 value.", "b");
  161. }
  162. }
  163. }