// // UniWebViewAuthenticationUtils.cs // Created by Wang Wei (@onevcat) on 2022-06-25. // // This file is a part of UniWebView Project (https://uniwebview.com) // By purchasing the asset, you are allowed to use this code in as many as projects // you want, only if you publish the final products under the name of the same account // used for the purchase. // // This asset and all corresponding files (such as source code) are provided on an // “as is” basis, without warranty of any kind, express of implied, including but not // limited to the warranties of merchantability, fitness for a particular purpose, and // noninfringement. In no event shall the authors or copyright holders be liable for any // claim, damages or other liability, whether in action of contract, tort or otherwise, // arising from, out of or in connection with the software or the use of other dealing in the software. // using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Security.Cryptography; using System.Text; using UnityEngine.Networking; /// /// This class provides some helper utils for performing the authentication flow. /// /// They are used inside the built-in flows, but you can also use them to implement your own flow. /// public class UniWebViewAuthenticationUtils { internal static Dictionary ParseFormUrlEncodedString(string input) { var result = new Dictionary(); if (input.StartsWith("?") || input.StartsWith("#")) { input = input.Substring(1); } var pairs = input.Split('&'); foreach (var pair in pairs) { var kv = pair.Split('='); result.Add(UnityWebRequest.UnEscapeURL(kv[0]), UnityWebRequest.UnEscapeURL(kv[1])); } return result; } /// /// Generates a random Base64 encoded string. /// /// A random Base64 encoded string. public static string GenerateRandomBase64String() { var randomNumber = new byte[32]; string value = ""; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(randomNumber); value = Convert.ToBase64String(randomNumber); } return value; } /// /// Generates a random Base64URL encoded string. /// /// A random Base64URL encoded string. public static string GenerateRandomBase64URLString() { var value = GenerateRandomBase64String(); return ConvertToBase64URLString(value); } static readonly char[] padding = { '=' }; /// /// Converts a Base64 encoded string to a Base64URL encoded string. /// /// The Base64 encoded string. /// A string with Base64URL encoded for the input. public static string ConvertToBase64URLString(string input) { return input.TrimEnd(padding).Replace('+', '-').Replace('/', '_'); } /// /// Converts a Base64URL encoded string to a Base64 encoded string. /// /// The Base64URL encoded string. /// A string with Base64 encoded for the input. public static string ConvertToBase64String(string input) { string result = input.Replace('_', '/').Replace('-', '+'); switch (input.Length % 4) { case 2: result += "=="; break; case 3: result += "="; break; } return result; } /// /// Generates a code verifier for PKCE usage. /// /// The length of the target code verifier. Default is 64. /// A generated code verifier for PKCE usage. public static string GenerateCodeVerifier(int length = 64) { var randomNumber = new byte[32]; string value = ""; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(randomNumber); String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; Random random = new Random(System.BitConverter.ToInt32(randomNumber, 0)); value = new String(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); } return value; } /// /// Calculates the code challenge for PKCE usage, with a given code verifier and hash method. /// /// The code verifier you generated. /// The hash method you want to use. /// The result of the code challenge. public static string CalculateCodeChallenge(string codeVerifier, UniWebViewAuthenticationPKCE method) { switch (method) { case UniWebViewAuthenticationPKCE.None: throw new ArgumentOutOfRangeException(nameof(method), method, null); case UniWebViewAuthenticationPKCE.S256: var sha256 = SHA256.Create(); var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(codeVerifier)); return ConvertToBase64URLString(System.Convert.ToBase64String(hash)); case UniWebViewAuthenticationPKCE.Plain: return codeVerifier; default: throw new ArgumentOutOfRangeException(nameof(method), method, null); } } public static string ConvertPKCEToString(UniWebViewAuthenticationPKCE method) { switch (method) { case UniWebViewAuthenticationPKCE.None: return null; case UniWebViewAuthenticationPKCE.S256: return "S256"; case UniWebViewAuthenticationPKCE.Plain: return "plain"; } return null; } public static string ConvertIntentUri(string input) { var uri = new Uri(input); if (uri.Scheme != "intent") { return input; } var host = uri.Host; string scheme = null; var fragmentPairs = new Dictionary(); var fragments = uri.Fragment; fragments.Split(';').ToList().ForEach(fragment => { var kv = fragment.Split('='); if (kv.Length == 2 && kv[0] == "scheme") { scheme = kv[1]; } }); if (!String.IsNullOrEmpty(scheme)) { return scheme + "://" + host; } return input; } public static string CreateQueryString(NameValueCollection collection) { int count = collection.Count; if (count == 0) { return ""; } StringBuilder sb = new StringBuilder(); string [] keys = collection.AllKeys; for (int i = 0; i < count; i++) { sb.AppendFormat ("{0}={1}&", keys[i], UnityWebRequest.EscapeURL(collection[keys[i]])); } if (sb.Length > 0) { sb.Length--; } return sb.ToString(); } } public enum UniWebViewAuthenticationPKCE { None, S256, Plain }