c# - sheets - Acceso a las API de GData(API de hoja de cálculo) más antiguas mediante OAuth 2 y una cuenta de servicio
google spreadsheet api javascript (3)
Logré resolver esto subclasificando GDataRequestFactory y creando mi propia implementación de las interfaces implementadas por GDataRequest. Esta implementación ajusta una instancia de GDataRequest instanciada a través de la reflexión, y agrega el código necesario para realizar la autenticación utilizando una instancia de IAuthenticator (en mi caso, Auth2Authenticator).
Escribí una publicación de blog en él y agregué un ejemplo como una Gist:
- Blog: Uso de la API de hoja de cálculo de Google mediante .NET, OAuth 2.0 y una cuenta de servicio
- Gist 4244834
Siéntase libre de usar esto si le ayuda (licencia BSD).
La pregunta corta es si esto es posible y, de ser así, ¿cómo?
contorno
Tengo una aplicación .NET que actualmente utiliza una cuenta de servicio para acceder a la información en un dominio de Google Apps mediante la API de Google Drive. Esto funciona bien usando la biblioteca de google-api-dotnet-client y el código siguiendo las mismas líneas que se muestran en las muestras aquí , que actualmente son un buen ejemplo básico de lo que estoy haciendo.
Lo que quiero hacer ahora es extenderlo así como usar las API proporcionadas por la "nueva" biblioteca de google-api-dotnet-client, utiliza las bibliotecas más antiguas "GData", tal como se proporciona a través de la biblioteca más antigua de google-gdata. , específicamente la API de hojas de cálculo (y quizás más por venir).
El problema
Aquí es donde surge la dificultad. La biblioteca anterior hace exactamente lo que quiero, como lo demuestra el segundo enlace en el primer párrafo anterior, y el hecho de que lo haga yo mismo. SIN EMBARGO ... aunque la segunda biblioteca se ha actualizado para admitir OAuth 2.0 además de OAuth 1.0 y las otras técnicas de autenticación más antiguas, no lo permite, por lo que puedo decir de las extensas búsquedas en Google y rastro y error, permite el " Cuenta de servicio en nombre de la operación de todos mis usuarios que necesito.
Mi pregunta es si me estoy perdiendo algo (posiblemente algo difícil de encontrar o algo no documentado) que me permita hacer lo que quiero. Si eso falla, ¿hay alguna manera de forzar este comportamiento y hacer que estas dos bibliotecas operen en paralelo?
La solucion ideal
Idealmente, me encantaría tener alguna forma de que la instancia de Google.GData.Spreadsheets.SpreadsheetsService
pudiera aprovechar la instancia de Google.GData.Spreadsheets.SpreadsheetsService
Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient>
que ya estoy usando ... de alguna manera. ¿Es posible tal brujería? Me estoy perdiendo lo obvio?
Si eso falla, me alegro de volver a hacer el baile completo del "cliente de flujo de afirmación" de OAuth2 si es necesario, de alguna manera que la biblioteca más antigua pueda manejar.
¿Ayuda?
otros pensamientos
He considerado, y por el momento he rechazado, la opción de empezar de cero y escribir mi propia biblioteca para que esto suceda. Esto es por dos razones:
- La biblioteca gdata ya existe, y ha sido desarrollada por muchas personas probablemente más inteligentes que yo. No soy tan arrogante que creo que puedo hacerlo mejor.
- No estoy seguro de que el enfoque OAuth2 con cuenta de servicio sea compatible / permitido incluso en estas API anteriores.
Un enfoque alternativo que he estado esperando evitar pero que podría tener que recurrir a las respuestas aquí será usar OAuth 1.0 de dos patas para partes de esto. Preferiría no hacerlo, ya que tener partes de la aplicación se basan en un método de autenticación antiguo mientras que otras partes lo hacen de la mejor manera que me parece mal. Y hay mucho más para salir mal ...
Actualizaciones
He considerado la posibilidad de subclasificar GDataRequestFactory y GDataRequest para poder hacer mi propia fábrica de solicitudes y hacer que tome la instancia de Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient>
(bueno, una instancia de Google.Apis.Authentication.IAuthenticator
todos modos) que podría intervenir para autenticar la solicitud justo antes de que se llame. Sin embargo ... el constructor para GDataRequest es interno, lo que me ha detenido.
Realmente parece que esto no está destinado a ser.
Oye, simplemente tropezé con el mismo problema y encontré una solución diferente:
¿Alguien ha pensado alguna vez en escribir los parámetros del objeto credenciales directamente en un objeto OAuth2Parameters?
Hice esto y funcionó muy bien:
public class OAuthTest
{
OAuth2Parameters param = new OAuth2Parameters();
public OAuthTest()
{
Debug.WriteLine("Calling: AuthGoogleDataInterface()");
bool init = AuthGoogleDataInterface();
if (init)
{
GOAuth2RequestFactory requestFactory = new GOAuth2RequestFactory(null, "My App User Agent", this.param);
//requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
var service = new SpreadsheetsService("MyService");
service.RequestFactory = requestFactory;
SpreadsheetQuery query = new SpreadsheetQuery();
// Make a request to the API and get all spreadsheets.
SpreadsheetFeed feed = service.Query(query);
// Iterate through all of the spreadsheets returned
foreach (SpreadsheetEntry entry in feed.Entries)
{
// Print the title of this spreadsheet to the screen
Debug.WriteLine(entry.Title.Text);
}
}
Debug.WriteLine(m_Init);
}
private bool AuthGoogleDataInterface()
{
bool b_success;
try
{
Console.WriteLine("New User Credential");
// New User Credential
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
GoogleClientSecrets GCSecrets = GoogleClientSecrets.Load(stream);
string[] ArrScope = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" };
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GCSecrets.Secrets,
ArrScope,
"user", CancellationToken.None,
new FileDataStore("My.cal")).Result;
// put the Information generated for the credentials object into the OAuth2Parameters-Object to access the Spreadsheets
this.param.ClientId = GCSecrets.Secrets.ClientId; //CLIENT_ID;
this.param.ClientSecret = GCSecrets.Secrets.ClientSecret; //CLIENT_SECRET;
this.param.RedirectUri = "urn:ietf:wg:oauth:2.0:oob"; //REDIRECT_URI;
this.param.Scope = ArrScope.ToString();
this.param.AccessToken = credential.Token.AccessToken;
this.param.RefreshToken = credential.Token.RefreshToken;
}
Debug.WriteLine("AuthGoogleDataInterface: Success");
b_success = true;
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
b_success = false;
}
return b_success;
}
}
Por el bien de otras personas que se encuentran con esta pregunta (ahora que la solución vinculada a la respuesta aceptada utiliza un código en desuso), así lo resolví:
Primero, comience en "new API API" (use el paquete nuget Google.Apis.Auth) configurando un ServiceAccountCredential
siguiendo el ejemplo de la cuenta de servicio de Google:
//In the old api, this accessed the main api accounts'' sheets, not anymore
//** Important ** share spreadsheets with the Service Account by inviting the "serviceAccountEmail" address to the sheet
string serviceAccountEmail = "[email protected]";
var certificate = new X509Certificate2(@"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" }
}.FromCertificate(certificate));
Indique la credencial para solicitar un token de acceso:
credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait();
Ahora es el momento de volver a la tierra "antigua API" (use el paquete de Google.GData.Spreadsheets
Google.GData.Spreadsheets). Comience por construir el SpreadsheetsService
(similar al example de Google):
SpreadsheetsService service = new SpreadsheetsService("MySpreadsheetIntegration-v1");
Para usar la autenticación de la cuenta de servicio, crearemos una instancia de GDataRequestFactory
y estableceremos un encabezado de Authorization
personalizado:
var requestFactory = new GDataRequestFactory("My App User Agent");
requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
Finalmente, establezca la propiedad RequestFactory
SpreadsheetsService
en esta nueva fábrica:
service.RequestFactory = requestFactory;
Y siga adelante y use el Servicio de SpreadsheetsService
como lo habría autenticado utilizando cualquier otra técnica. ( Sugerencia: comparta hojas de cálculo con la cuenta de servicio invitando a la dirección de serviceAccountEmail
electrónico de cuenta de servicio)