AndroidPublisherService - Play Developer API 客户端 - 由于凭据错误,上传 aab 失败

AndroidPublisherService - Play Developer API Client - Upload aab failes due to bad credentials

正在尝试使用来自 Play Developer API 客户端的 AndroidPublisherService。 我可以列出活动曲目和这些曲目中的版本,但是当我尝试上传新版本时,似乎无法附加先前已进行的身份验证以读取数据。

我已经使用 var googleCredentials = GoogleCredential.FromStream(keyDataStream) .CreateWithUser(serviceUsername); 进行了身份验证,其中 serviceUsername 是我的服务帐户的电子邮件地址。

    private static void Execute(string packageName, string aabfile, string credfile, string serviceUsername)
    {
        var credentialsFilename = credfile;
        if (string.IsNullOrWhiteSpace(credentialsFilename))
        {
            // Check env. var
            credentialsFilename =
                Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS",
                    EnvironmentVariableTarget.Process);
        }

        Console.WriteLine($"Using credentials {credfile} with package {packageName} for aab file {aabfile}");
        
        var keyDataStream = File.OpenRead(credentialsFilename);
        var googleCredentials = GoogleCredential.FromStream(keyDataStream)
            .CreateWithUser(serviceUsername);
        var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
        
        var service = new AndroidPublisherService();
        
        var edit = service.Edits.Insert(new AppEdit { ExpiryTimeSeconds = "3600" }, packageName);
        edit.Credential = credentials;
        var activeEditSession = edit.Execute();
        Console.WriteLine($"Edits started with id {activeEditSession.Id}");
        
        var tracksList = service.Edits.Tracks.List(packageName, activeEditSession.Id);
        tracksList.Credential = credentials;
        var tracksResponse = tracksList.Execute();
        foreach (var track in tracksResponse.Tracks)
        {
            Console.WriteLine($"Track: {track.TrackValue}");
            Console.WriteLine("Releases: ");
            foreach (var rel in track.Releases)
                Console.WriteLine($"{rel.Name} version: {rel.VersionCodes.FirstOrDefault()} - Status: {rel.Status}");
        }
        
        using var fileStream = File.OpenRead(aabfile);
        var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream");
        var uploadProgress = upload.Upload();
        if (uploadProgress == null || uploadProgress.Exception != null)
        {
            Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
            return;
        }
        
        Console.WriteLine($"Upload {uploadProgress.Status}");

        var tracksUpdate = service.Edits.Tracks.Update(new Track
        {
            Releases = new List<TrackRelease>(new[]
            {
                new TrackRelease
                {
                    Name = "Roswell - Grenis Dev Test",
                    Status = "completed",
                    VersionCodes = new List<long?>(new[] {(long?) upload?.ResponseBody?.VersionCode})
                }
            })
        }, packageName, activeEditSession.Id, "internal");
        
        tracksUpdate.Credential = credentials;
        var trackResult = tracksUpdate.Execute();
        Console.WriteLine($"Track {trackResult?.TrackValue}");

        var commitResult = service.Edits.Commit(packageName, activeEditSession.Id);
        Console.WriteLine($"{commitResult.EditId} has been committed");
    }

正如代码所指出的,所有操作对象(例如 tracksList.Credential = credentials;)都可以提供从服务帐户生成的凭据。

但是 实际的上传操作 var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream"); 不会公开 .Credential 对象,并且总是失败:

    The service androidpublisher has thrown an exception: Google.GoogleApiException: Google.Apis.Requests.RequestError
Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. [401]
Errors [
        Message[Login Required.] Location[Authorization - header] Reason[required] Domain[global]
]

   at Google.Apis.Upload.ResumableUpload`1.InitiateSessionAsync(CancellationToken cancellationToken)
   at Google.Apis.Upload.ResumableUpload.UploadAsync(CancellationToken cancellationToken)

那么,我将如何使用此处给定的凭据提供实际的上传操作?

白天设法解决了这个问题,我在创建 GoogleCredential 对象时错过了对 CreateScoped() 的一次调用,以及对上传对象的 InitiateSession() 调用。

            var googleCredentials = GoogleCredential.FromStream(keyDataStream)
                .CreateWithUser(serviceUsername)
                .CreateScoped(AndroidPublisherService.Scope.Androidpublisher);

一旦完成,我就可以通过调用

获得有效的 oauth 令牌
            var googleCredentials = GoogleCredential.FromStream(keyDataStream)
                .CreateWithUser(serviceUsername)
                .CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
            
            var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
            var oauthToken = credentials?.GetAccessTokenForRequestAsync(AndroidPublisherService.Scope.Androidpublisher).Result;

我现在可以在上传请求中使用该 oauth 令牌:

            upload.OauthToken = oauthToken;
            _ = await upload.InitiateSessionAsync();
            
            var uploadProgress = await upload.UploadAsync();
            if (uploadProgress == null || uploadProgress.Exception != null)
            {
                Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
                return;
            }

成功将新的 aab 文件上传到 google Play 商店内部测试轨道的完整代码示例如下所示:

        private async Task UploadGooglePlayRelease(string fileToUpload, string changeLogFile, string serviceUsername, string packageName)
        {
            var serviceAccountFile = ResolveServiceAccountCertificateInfoFile();
            if (!serviceAccountFile.Exists)
                throw new ApplicationException($"Failed to find the service account certificate file. {serviceAccountFile.FullName}");
            
            var keyDataStream = File.OpenRead(serviceAccountFile.FullName);
            var googleCredentials = GoogleCredential.FromStream(keyDataStream)
                .CreateWithUser(serviceUsername)
                .CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
            
            var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
            var oauthToken = credentials?.GetAccessTokenForRequestAsync(AndroidPublisherService.Scope.Androidpublisher).Result;
            
            var service = new AndroidPublisherService();
            
            var edit = service.Edits.Insert(new AppEdit { ExpiryTimeSeconds = "3600" }, packageName);
            edit.Credential = credentials;
            var activeEditSession = await edit.ExecuteAsync();
            _logger.LogInformation($"Edits started with id {activeEditSession.Id}");
            
            var tracksList = service.Edits.Tracks.List(packageName, activeEditSession.Id);
            tracksList.Credential = credentials;
            var tracksResponse = await tracksList.ExecuteAsync();
            foreach (var track in tracksResponse.Tracks)
            {
                _logger.LogInformation($"Track: {track.TrackValue}");
                _logger.LogInformation("Releases: ");
                foreach (var rel in track.Releases)
                    _logger.LogInformation($"{rel.Name} version: {rel.VersionCodes.FirstOrDefault()} - Status: {rel.Status}");
            }

            var fileStream = File.OpenRead(fileToUpload);
            var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream");
            
            upload.OauthToken = oauthToken;
            _ = await upload.InitiateSessionAsync();
            
            var uploadProgress = await upload.UploadAsync();
            if (uploadProgress == null || uploadProgress.Exception != null)
            {
                Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
                return;
            }
            
            _logger.LogInformation($"Upload {uploadProgress.Status}");

            var releaseNotes = await File.ReadAllTextAsync(changeLogFile);

            var tracksUpdate = service.Edits.Tracks.Update(new Track
            {
                Releases = new List<TrackRelease>(new[]
                {
                    new TrackRelease
                    {
                        Name = $"{upload?.ResponseBody?.VersionCode}",
                        Status = "completed",
                        InAppUpdatePriority = 5,
                        CountryTargeting = new CountryTargeting { IncludeRestOfWorld = true },
                        ReleaseNotes = new List<LocalizedText>(new []{ new LocalizedText {  Language = "en-US", Text = releaseNotes } }),
                        VersionCodes = new List<long?>(new[] {(long?) upload?.ResponseBody?.VersionCode})
                    }
                })
            }, packageName, activeEditSession.Id, "internal");
            
            tracksUpdate.Credential = credentials;
            var trackResult = await tracksUpdate.ExecuteAsync();
            _logger.LogInformation($"Track {trackResult?.TrackValue}");

            var commitResult = service.Edits.Commit(packageName, activeEditSession.Id);
            commitResult.Credential = credentials;
            await commitResult.ExecuteAsync();
            
            _logger.LogInformation($"{commitResult.EditId} has been committed");
        }