diff --git a/Assets/UnityGoogleDrive/Editor/GoogleDriveSettingsEditor.cs b/Assets/UnityGoogleDrive/Editor/GoogleDriveSettingsEditor.cs index 856c2c0..ede3126 100644 --- a/Assets/UnityGoogleDrive/Editor/GoogleDriveSettingsEditor.cs +++ b/Assets/UnityGoogleDrive/Editor/GoogleDriveSettingsEditor.cs @@ -14,6 +14,7 @@ public class GoogleDriveSettingsEditor : Editor private SerializedProperty uriSchemeClientCredentials; private SerializedProperty accessScopes; private SerializedProperty loopbackUri; + private SerializedProperty loopbackPort; private SerializedProperty loopbackResponseHtml; private SerializedProperty accessTokenPrefsKey; private SerializedProperty refreshTokenPrefsKey; @@ -21,7 +22,8 @@ public class GoogleDriveSettingsEditor : Editor private static readonly GUIContent genericClientCredentialsContent = new GUIContent("Generic Credentials", "Google Drive API application credentials used to authorize requests via loopback and redirect schemes."); private static readonly GUIContent uriSchemeClientCredentialsContent = new GUIContent("URI Scheme Credentials", "Google Drive API application credentials used to authorize requests via custom URI scheme."); private static readonly GUIContent accessScopesContent = new GUIContent("Access Scopes", "Scopes of access to the user's Google Drive the app will request."); - private static readonly GUIContent loopbackUriContent = new GUIContent("Loopback URI", "A web address for the loopback authentication requests. Defult is 'localhost'."); + private static readonly GUIContent loopbackUriContent = new GUIContent("Loopback URI", "A web address for the loopback authentication requests. Default is 'localhost'."); + private static readonly GUIContent loopbackPortContent = new GUIContent("Loopback Port", "Specific port to use for loopback scheme, eg: 8888. Leave empty to find random unused port."); private static readonly GUIContent loopbackResponseHtmlContent = new GUIContent("Loopback Response HTML", "HTML page shown to the user when loopback response is received."); private static readonly GUIContent accessTokenPrefsKeyContent = new GUIContent("Access Token Key", "PlayerPrefs key used to store access token."); private static readonly GUIContent refreshTokenPrefsKeyContent = new GUIContent("Refresh Token Key", "PlayerPrefs key used to store refresh token."); @@ -59,6 +61,7 @@ private void OnEnable () uriSchemeClientCredentials = serializedObject.FindProperty("uriSchemeClientCredentials"); accessScopes = serializedObject.FindProperty("accessScopes"); loopbackUri = serializedObject.FindProperty("loopbackUri"); + loopbackPort = serializedObject.FindProperty("loopbackPort"); loopbackResponseHtml = serializedObject.FindProperty("loopbackResponseHtml"); accessTokenPrefsKey = serializedObject.FindProperty("accessTokenPrefsKey"); refreshTokenPrefsKey = serializedObject.FindProperty("refreshTokenPrefsKey"); @@ -76,6 +79,7 @@ public override void OnInspectorGUI () EditorGUILayout.PropertyField(uriSchemeClientCredentials, uriSchemeClientCredentialsContent, true); EditorGUILayout.PropertyField(accessScopes, accessScopesContent, true); EditorGUILayout.PropertyField(loopbackUri, loopbackUriContent); + EditorGUILayout.PropertyField(loopbackPort, loopbackPortContent); EditorGUILayout.PropertyField(loopbackResponseHtml, loopbackResponseHtmlContent); EditorGUILayout.PropertyField(accessTokenPrefsKey, accessTokenPrefsKeyContent); EditorGUILayout.PropertyField(refreshTokenPrefsKey, refreshTokenPrefsKeyContent); @@ -87,8 +91,7 @@ public override void OnInspectorGUI () using (new EditorGUI.DisabledScope(string.IsNullOrEmpty(TargetSettings.GenericClientCredentials.ProjectId))) if (GUILayout.Button("Manage Google Drive API app")) - Application.OpenURL(string.Format("https://console.developers.google.com/apis/credentials?project={0}", - TargetSettings.GenericClientCredentials.ProjectId)); + Application.OpenURL($"https://console.developers.google.com/apis/credentials?project={TargetSettings.GenericClientCredentials.ProjectId}"); if (GUILayout.Button("Parse generic credentials JSON file...")) ParseGenericCredentialsJson(EditorUtility.OpenFilePanel("Select Drive API app credentials JSON file", "", "json")); diff --git a/Assets/UnityGoogleDrive/Runtime/Authorization/LoopbackAccessTokenProvider.cs b/Assets/UnityGoogleDrive/Runtime/Authorization/LoopbackAccessTokenProvider.cs index c895eb0..da17954 100644 --- a/Assets/UnityGoogleDrive/Runtime/Authorization/LoopbackAccessTokenProvider.cs +++ b/Assets/UnityGoogleDrive/Runtime/Authorization/LoopbackAccessTokenProvider.cs @@ -104,7 +104,9 @@ private void ExecuteFullAuth () var codeChallenge = CryptoUtils.Base64UriEncodeNoPadding(codeVerifierHash); // Creates a redirect URI using an available port on the loopback address. - redirectUri = $"{settings.LoopbackUri}:{GetRandomUnusedPort()}"; + var port = string.IsNullOrWhiteSpace(settings.LoopbackPort) + ? GetRandomUnusedPort() : int.Parse(settings.LoopbackPort); + redirectUri = $"{settings.LoopbackUri}:{port}"; // Listen for requests on the redirect URI. var httpListener = new HttpListener(); @@ -113,9 +115,10 @@ private void ExecuteFullAuth () // Create the OAuth 2.0 authorization request. // https://developers.google.com/identity/protocols/OAuth2WebServer#creatingclient - var authRequest = string.Format("{0}?response_type=code&scope={1}&redirect_uri={2}&client_id={3}&state={4}&code_challenge={5}&code_challenge_method={6}" + - "&access_type=offline" + // Forces to return a refresh token at the auth code exchange phase. - "&approval_prompt=force", // Forces to show consent screen for each auth request. Needed to return refresh tokens on consequent auth runs. + var authRequest = string.Format( + "{0}?response_type=code&scope={1}&redirect_uri={2}&client_id={3}&state={4}&code_challenge={5}&code_challenge_method={6}" + + "&access_type=offline" + // Forces to return a refresh token at the auth code exchange phase. + "&approval_prompt=force", // Forces to show consent screen for each auth request. Needed to return refresh tokens on consequent auth runs. settings.GenericClientCredentials.AuthUri, Uri.EscapeDataString(settings.AccessScope), Uri.EscapeDataString(redirectUri), diff --git a/Assets/UnityGoogleDrive/Runtime/GoogleDriveSettings.cs b/Assets/UnityGoogleDrive/Runtime/GoogleDriveSettings.cs index 471a38f..9e8ac5e 100644 --- a/Assets/UnityGoogleDrive/Runtime/GoogleDriveSettings.cs +++ b/Assets/UnityGoogleDrive/Runtime/GoogleDriveSettings.cs @@ -35,6 +35,10 @@ public class GoogleDriveSettings : ScriptableObject /// public string LoopbackUri => loopbackUri; /// + /// Specific port to use for loopback scheme. When empty find random unused port instead. + /// + public string LoopbackPort => loopbackPort; + /// /// HTML page shown to the user when loopback response is received. /// public string LoopbackResponseHtml { get => loopbackResponseHtml; set => loopbackResponseHtml = value; } @@ -51,6 +55,7 @@ public class GoogleDriveSettings : ScriptableObject [SerializeField] private UriSchemeClientCredentials uriSchemeClientCredentials; [SerializeField] private List accessScopes = new List { "https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/drive.appdata" }; [SerializeField] private string loopbackUri = "http://localhost"; + [SerializeField] private string loopbackPort = ""; [SerializeField] private string loopbackResponseHtml = "

Please return to the app.

"; [SerializeField] private string accessTokenPrefsKey = "GoogleDriveAccessToken"; [SerializeField] private string refreshTokenPrefsKey = "GoogleDriveRefreshToken"; diff --git a/Assets/UnityGoogleDrive/package.json b/Assets/UnityGoogleDrive/package.json index e387c36..5f2890f 100644 --- a/Assets/UnityGoogleDrive/package.json +++ b/Assets/UnityGoogleDrive/package.json @@ -1,6 +1,6 @@ { "name": "com.elringus.unitygoogledrive", - "version": "0.28.0", + "version": "0.29.0", "displayName": "UnityGoogleDrive", "description": "Google Drive SDK for Unity game engine", "unity": "2019.4", diff --git a/UnityGoogleDrive.unitypackage b/UnityGoogleDrive.unitypackage index d000afb..f6cb634 100644 Binary files a/UnityGoogleDrive.unitypackage and b/UnityGoogleDrive.unitypackage differ