// // UniWebViewAuthenticationFlowGitHub.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 UnityEngine; using UnityEngine.Events; using System.Collections.Generic; using System; /// /// A predefined authentication flow for GitHub. /// /// This implementation follows the flow described here: /// https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps /// /// See https://docs.uniwebview.com/guide/oauth2.html for a more detailed guide of authentication in UniWebView. /// public class UniWebViewAuthenticationFlowGitHub: UniWebViewAuthenticationCommonFlow, IUniWebViewAuthenticationFlow { /// /// The client ID of your GitHub application. /// public string clientId = ""; /// /// The client secret of your GitHub application. /// public string clientSecret = ""; /// /// The callback URL of your GitHub application. /// public string callbackUrl = ""; /// /// Optional to control this flow's behaviour. /// public UniWebViewAuthenticationFlowGitHubOptional optional; private readonly UniWebViewAuthenticationConfiguration config = new UniWebViewAuthenticationConfiguration( "https://github.com/login/oauth/authorize", "https://github.com/login/oauth/access_token" ); /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// [field: SerializeField] public UnityEvent OnAuthenticationFinished { get; set; } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// [field: SerializeField] public UnityEvent OnAuthenticationErrored { get; set; } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// [field: SerializeField] public UnityEvent OnRefreshTokenFinished { get; set; } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// [field: SerializeField] public UnityEvent OnRefreshTokenErrored { get; set; } /// /// Starts the authentication flow with the standard OAuth 2.0. /// This implements the abstract method in `UniWebViewAuthenticationCommonFlow`. /// public override void StartAuthenticationFlow() { var flow = new UniWebViewAuthenticationFlow(this); flow.StartAuth(); } /// /// Starts the refresh flow with the standard OAuth 2.0. /// This implements the abstract method in `UniWebViewAuthenticationCommonFlow`. /// /// The refresh token received with a previous access token response. public override void StartRefreshTokenFlow(string refreshToken) { var flow = new UniWebViewAuthenticationFlow(this); flow.RefreshToken(refreshToken); } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// public Dictionary GetAuthenticationUriArguments() { var authorizeArgs = new Dictionary { { "client_id", clientId } }; if (optional != null) { if (!String.IsNullOrEmpty(optional.redirectUri)) { authorizeArgs.Add("redirect_uri", optional.redirectUri); } if (!String.IsNullOrEmpty(optional.login)) { authorizeArgs.Add("login", optional.login); } if (!String.IsNullOrEmpty(optional.scope)) { authorizeArgs.Add("scope", optional.scope); } if (optional.enableState) { var state = GenerateAndStoreState(); authorizeArgs.Add("state", state); } if (!optional.allowSignup) { // The default value is true. authorizeArgs.Add("allow_signup", "false"); } } return authorizeArgs; } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// public string GetCallbackUrl() { return callbackUrl; } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// public UniWebViewAuthenticationConfiguration GetAuthenticationConfiguration() { return config; } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// public Dictionary GetAccessTokenRequestParameters(string authResponse) { if (!authResponse.StartsWith(callbackUrl)) { throw AuthenticationResponseException.UnexpectedAuthCallbackUrl; } var uri = new Uri(authResponse); var response = UniWebViewAuthenticationUtils.ParseFormUrlEncodedString(uri.Query); if (!response.TryGetValue("code", out var code)) { throw AuthenticationResponseException.InvalidResponse(authResponse); } if (optional.enableState) { VerifyState(response); } var result = new Dictionary { { "client_id", clientId }, { "client_secret", clientSecret }, { "code", code } }; if (optional != null && String.IsNullOrEmpty(optional.redirectUri)) { result.Add("redirect_uri", optional.redirectUri); } return result; } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// public Dictionary GetRefreshTokenRequestParameters(string refreshToken) { return new Dictionary { { "client_id", clientId }, { "client_secret", clientSecret }, { "refresh_token", refreshToken }, { "grant_type", "refresh_token" } }; } /// /// Implements required method in `IUniWebViewAuthenticationFlow`. /// public UniWebViewAuthenticationGitHubToken GenerateTokenFromExchangeResponse(string exchangeResponse) { return new UniWebViewAuthenticationGitHubToken(exchangeResponse); } } /// /// The authentication flow's optional settings for GitHub. /// [Serializable] public class UniWebViewAuthenticationFlowGitHubOptional { /// /// The redirect URI should be used in exchange token request. /// public string redirectUri = ""; /// /// Suggests a specific account to use for signing in and authorizing the app. /// public string login = ""; /// /// The scope string of all your required scopes. /// public string scope = ""; /// /// Whether to enable the state verification. If enabled, the state will be generated and verified in the /// authentication callback. /// public bool enableState = false; /// /// Whether or not unauthenticated users will be offered an option to sign up for GitHub during the OAuth flow. /// public bool allowSignup = true; } /// /// The token object from GitHub. /// public class UniWebViewAuthenticationGitHubToken { /// The access token retrieved from the service provider. public string AccessToken { get; } /// The granted scopes of the token. public string Scope { get; } /// The token type. Usually `bearer`. public string TokenType { get; } /// The refresh token retrieved from the service provider. public string RefreshToken { get; } /// Expiration duration for the refresh token. public long RefreshTokenExpiresIn { get; } /// /// The raw value of the response of the exchange token request. /// If the predefined fields are not enough, you can parse the raw value to get the extra information. /// public string RawValue { get; } public UniWebViewAuthenticationGitHubToken(string result) { RawValue = result; var values = UniWebViewAuthenticationUtils.ParseFormUrlEncodedString(result); AccessToken = values.ContainsKey("access_token") ? values["access_token"] : null ; if (AccessToken == null) { throw AuthenticationResponseException.InvalidResponse(result); } Scope = values.ContainsKey("scope") ? values["scope"] : null; TokenType = values.ContainsKey("token_type") ? values["token_type"] : null; RefreshToken = values.ContainsKey("refresh_token") ? values["refresh_token"] : null; RefreshTokenExpiresIn = values.ContainsKey("refresh_token_expires_in") ? long.Parse(values["refresh_token_expires_in"]) : 0; } }