client/visualStudio/IdesLspPoc/LspClient/S3/S3CredentialsUpdater.cs (101 lines of code) (raw):

using Amazon.Runtime; using Amazon.Runtime.CredentialManagement; using IdesLspPoc.Credentials; using IdesLspPoc.Output; using Jose; using Microsoft.VisualStudio.Threading; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using StreamJsonRpc; using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Timers; namespace IdesLspPoc.LspClient.S3 { /// <summary> /// This class knows how to push credentials over to the language server (via SendIamCredentialsAsync) /// PROOF OF CONCEPT - this class would be used in a product like the AWS Toolkit /// to push credentials to the server whenever the credentials state changes (eg: user selects another profile, or /// credentials expire). For this concept, it is resolving and pushing credentials every 10 seconds as a /// simulation of the Toolkit sending credentials to the server. /// This class would also know how to clear credentials from the language server. /// </summary> internal class S3CredentialsUpdater { private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy(), } }; private Timer _resendTimer; private OutputWindow _outputWindow; private JsonRpc _rpc; private byte[] _aesKey; public S3CredentialsUpdater(JsonRpc rpc, byte[] aesKey, OutputWindow outputWindow) { this._rpc = rpc; _aesKey = aesKey; _outputWindow = outputWindow; } /// <summary> /// We start sending the lsp server credentials every 10 seconds as a simulation of the credentials state changing /// </summary> public void StartCredentialsRefreshSimulation() { _resendTimer?.Stop(); _resendTimer = new Timer() { AutoReset = true, Interval = 10_000, }; _resendTimer.Elapsed += OnRefreshCredentials; _resendTimer.Start(); } private void OnRefreshCredentials(object sender, ElapsedEventArgs e) { // PROOF OF CONCEPT // We will resolve the default profile from the local system. // In a product, the host extension would know which profile it is configured to provide to the language server. var creds = new SharedCredentialsFile(); if (!creds.TryGetProfile("default", out var profile)) { _outputWindow.WriteLine("Client: Unable to get default profile"); return; } Task.Run(async () => { AWSCredentials awsCredentials = profile.GetAWSCredentials(creds); var request = CreateUpdateCredentialsRequest(await awsCredentials.GetCredentialsAsync(), _aesKey); await SendIamCredentialsAsync(request); }).Forget(); } public async Task SendIamCredentialsAsync(UpdateCredentialsRequest request) { _outputWindow.WriteLine("Client: Sending (simulated) refreshed Credentials to the server"); await this._rpc.InvokeAsync("aws/credentials/iam/update", request); } private static UpdateCredentialsRequest CreateUpdateCredentialsRequest(ImmutableCredentials credentials, byte[] aesKey) { var requestData = new UpdateIamCredentialsRequestData { AccessKeyId = credentials.AccessKey, SecretAccessKey = credentials.SecretKey, SessionToken = credentials.Token, }; return CreateUpdateCredentialsRequest(requestData, aesKey); } /// <summary> /// Creates an "update credentials" request that contains encrypted data /// </summary> private static UpdateCredentialsRequest CreateUpdateCredentialsRequest(object data, byte[] aesKey) { var payload = new Dictionary<string, object>() { { "data", data }, }; // We are handling the JSON serialization (instead of JWT.Encode) to ensure the fields are shaped with the correct casing. // Otherwise, the server may not receive the expected fields. var payloadJson = JsonConvert.SerializeObject(payload); var notBefore = new DateTimeOffset(DateTime.UtcNow.AddMinutes(-1)).ToUnixTimeSeconds(); var expiresOn = new DateTimeOffset(DateTime.UtcNow.AddMinutes(1)).ToUnixTimeSeconds(); var headers = new Dictionary<string, object>() { { "nbf", notBefore }, { "exp", expiresOn }, }; string jwt = JWT.Encode( payloadJson, aesKey, JweAlgorithm.DIR, JweEncryption.A256GCM, null, headers); return new UpdateCredentialsRequest { Data = jwt, }; } } }