sources/Google.Solutions.Apis/GoogleApiExceptionExtensions.cs (157 lines of code) (raw):
//
// Copyright 2020 Google LLC
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Json;
using Google.Solutions.Apis.Diagnostics;
using Google.Solutions.Common.Linq;
using Google.Solutions.Common.Util;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Google.Solutions.Apis
{
public static class GoogleApiExceptionExtensions
{
public static bool IsConstraintViolation(this GoogleApiException e)
{
return e.Error != null && e.Error.Code == 412;
}
public static bool IsAccessDenied(this GoogleApiException e)
{
return e.Error != null && e.Error.Code == 403;
}
public static bool IsNotFound(this GoogleApiException e)
{
return e.Error != null && e.Error.Code == 404;
}
public static bool IsBadRequest(this GoogleApiException e)
{
return e.Error != null && e.Error.Code == 400;
}
public static bool IsBadRequestCausedByServiceAccountAccessDenied(
this GoogleApiException e)
{
return IsBadRequest(e) && e.Error
.Errors
.EnsureNotNull()
.FirstOrDefault()
.Message == "SERVICE_ACCOUNT_ACCESS_DENIED";
}
public static bool IsReauthError(this Exception e)
{
//
// The TokenResponseException might be hiding in
// an AggregateException
//
e = e.Unwrap();
if (e is TokenResponseException tokenException)
{
return tokenException.Error.Error == "invalid_grant";
}
else
{
return false;
}
}
public static bool IsAccessDeniedByVpcServiceControlPolicy(
this GoogleApiException e)
{
return e.IsAccessDenied() &&
!string.IsNullOrEmpty(e.Message) &&
e.Message.Contains("vpcServiceControlsUniqueIdentifier");
}
/// <summary>
/// Extract VPC SC troubleshooting ID.
/// </summary>
/// <returns>ID or null</returns>
public static string? VpcServiceControlTroubleshootingId(
this GoogleApiException e)
{
var rawJson = e.Error?.ErrorResponseContent;
if (string.IsNullOrWhiteSpace(rawJson))
{
return null;
}
try
{
return NewtonsoftJsonSerializer.Instance
.Deserialize<ErrorEnvelope>(rawJson)?
.Error?
.Details?
.EnsureNotNull()
.Where(d => d.Violations != null)
.SelectMany(d => d.Violations)
.EnsureNotNull()
.FirstOrDefault(v => v.Type == "VPC_SERVICE_CONTROLS")?
.Description;
}
catch (JsonException)
{
return null;
}
}
public static HelpTopic? VpcServiceControlTroubleshootingLink(
this GoogleApiException e)
{
if (e.VpcServiceControlTroubleshootingId() is var id && id != null)
{
return new HelpTopic(
"VPC Service Controls Troubleshooter",
$"https://console.cloud.google.com/security/" +
$"service-perimeter/troubleshoot;uniqueId={id}");
}
else
{
return null;
}
}
public static bool IsAccessDeniedError(this Exception e)
{
return e.Unwrap() is GoogleApiException apiEx &&
apiEx.Error.Code == 403;
}
//---------------------------------------------------------------------
// JSON deserialization.
//---------------------------------------------------------------------
public class ErrorEnvelope
{
[JsonConstructor]
public ErrorEnvelope([JsonProperty("error")] ErrorSection error)
{
this.Error = error;
}
[JsonProperty("error")]
public ErrorSection Error { get; }
}
public class DetailSection
{
[JsonConstructor]
public DetailSection(
[JsonProperty("@type")] string type,
[JsonProperty("violations")] List<ViolationSection> violations)
{
this.Type = type;
this.Violations = violations;
}
[JsonProperty("@type")]
public string Type { get; }
[JsonProperty("violations")]
public IReadOnlyList<ViolationSection> Violations { get; }
}
public class ErrorSection
{
[JsonConstructor]
public ErrorSection(
[JsonProperty("details")] List<DetailSection> details)
{
this.Details = details;
}
[JsonProperty("details")]
public IReadOnlyList<DetailSection> Details { get; }
}
public class ViolationSection
{
[JsonConstructor]
public ViolationSection(
[JsonProperty("type")] string type,
[JsonProperty("description")] string description)
{
this.Type = type;
this.Description = description;
}
[JsonProperty("type")]
public string Type { get; }
[JsonProperty("description")]
public string Description { get; }
}
}
}