18using System.Collections.Generic;
19using System.Collections.Specialized;
20using System.Diagnostics;
25using System.Security.Cryptography;
27using System.Text.Json;
28using System.Text.Json.Serialization;
29using System.Threading;
30using System.Threading.Tasks;
34using Org.BouncyCastle.Crypto;
35using Org.BouncyCastle.OpenSsl;
36using Org.BouncyCastle.Security;
48 private static string ConfigFilePath {
get;
set; } =
"./credentialsConfig.json";
63 public string GoogleAuthBase {
get;
set; } =
"https://oauth2.googleapis.com";
89 public string AzureAuthBase {
get;
set; } =
"https://login.microsoftonline.com/common";
103 public string AWSAuthBase {
get;
set; } =
"https://your-domain.auth.{region}.amazoncognito.com";
175 if (file !=
null) ConfigFilePath = file.Filename;
176 if (
true == File.Exists(ConfigFilePath))
188 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"Failed to load credentials config from file",
null, GPALObjectType.None, ex);
203 if (file !=
null) ConfigFilePath = file.Filename;
213 public class AuthCodePayload
218 public string Code {
get;
set; }
230 public class GoogleServiceAccountKey
235 public string Type {
get;
set; }
284 [JsonPropertyName(
"access_token")]
290 [JsonPropertyName(
"expires_in")]
296 [JsonPropertyName(
"refresh_token")]
302 [JsonPropertyName(
"token_type")]
310 internal class BitwardenVaultResponse
315 public List<BitwardenVaultItem> Data {
get;
set; }
321 internal class BitwardenVaultItem
326 public BitwardenLogin Login {
get;
set; }
332 internal class BitwardenLogin
337 public string Username {
get;
set; }
341 public string Password {
get;
set; }
345 public List<BitwardenUri> Uris {
get;
set; }
351 internal class BitwardenUri
356 public string Uri {
get;
set; }
367 [JsonPropertyName(
"access_token")]
373 [JsonPropertyName(
"expires_in")]
379 [JsonPropertyName(
"refresh_token")]
385 [JsonPropertyName(
"token_type")]
397 [JsonPropertyName(
"access_token")]
403 [JsonPropertyName(
"expires_in")]
409 [JsonPropertyName(
"refresh_token")]
415 [JsonPropertyName(
"scope")]
421 [JsonPropertyName(
"token_type")]
433 [JsonPropertyName(
"access_token")]
439 [JsonPropertyName(
"expires_in")]
445 [JsonPropertyName(
"refresh_token")]
451 [JsonPropertyName(
"token_type")]
457 [JsonPropertyName(
"id_token")]
469 public string Id {
get;
set; }
473 public string Name {
get;
set; }
477 public string Url {
get;
set; }
500 public string Id {
get;
set; }
512 public List<OnePasswordField>
Fields {
get;
set; } =
new List<OnePasswordField>();
516 public List<OnePasswordUrl>
Urls {
get;
set; }
527 public string Href {
get;
set; }
538 public string Id {
get;
set; }
546 public string Type {
get;
set; }
565 public string Id {
get;
set; }
573 public string Url {
get;
set; }
585 public string Otp {
get;
set; }
635 public string Url {
get;
set; }
638 [InProgress(
"Credential class to get access tokens and credentials from password managers. Untested. 50%")]
644 internal CredentialServiceType ServiceType {
get;
private set; }
652 internal string Password {
get;
private set; }
681 public string Target {
get;
private set; }
685 public string Domain {
get;
private set; }
713 internal AzureTokenResponse AzureTokenResponse {
get;
set; }
717 internal BitwardenTokenResponse BitwardenTokenResponse {
get;
set; }
721 internal string ServiceAccountKeyJson {
get;
set; }
725 internal DateTime IssueTime {
get;
set; }
726 private int _expiresIn = 0;
730 internal int ExpiresIn
736 ExpiresAt = DateTime.UtcNow.AddSeconds(_expiresIn);
742 internal DateTime ExpiresAt {
get;
set; }
746 internal Browser.Browser Browser {
get;
set; }
756 internal static (string, string) SafeSplit(
string value)
758 if (
string.IsNullOrEmpty(value))
return (
"",
"");
759 var parts = value.Split(
new[] {
':' }, 2);
760 return (parts[0], parts.Length > 1 ? parts[1] :
"");
767 internal static bool MatchesDomain(
string url,
string domain)
769 if (
string.IsNullOrEmpty(domain))
return true;
770 if (
string.IsNullOrEmpty(url))
return false;
771 return url.IndexOf(domain, StringComparison.OrdinalIgnoreCase) >= 0;
777 internal static bool MatchesDomain(IEnumerable<string> urls,
string domain)
779 if (
string.IsNullOrEmpty(domain))
return true;
780 if (urls ==
null)
return false;
781 return urls.Any(url => MatchesDomain(url, domain));
788 private void EnsureValidToken(out
string accessToken)
790 if (IsTokenExpired())
800 internal static string GetScopeValue(
OAuthScope scope)
806 return "https://www.googleapis.com/auth/spreadsheets";
808 return "https://www.googleapis.com/auth/drive";
810 return "https://www.googleapis.com/auth/cloud-platform";
814 return "https://graph.microsoft.com/User.Read";
816 return "https://graph.microsoft.com/Mail.Read";
818 return "https://storage.azure.com/user_impersonation";
820 return "https://management.azure.com/user_impersonation";
831 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Unsupported scope: [{scope}]", scope, GPALObjectType.Other);
883 ServiceType = serviceType;
932 Config.GoogleRestRedirectUri = redirectUrl;
989 var parts = SafeSplit(key);
1003 string key = Environment.GetEnvironmentVariable(envVariableName, EnvironmentVariableTarget.Process)
1004 ?? Environment.GetEnvironmentVariable(envVariableName, EnvironmentVariableTarget.User)
1005 ?? Environment.GetEnvironmentVariable(envVariableName, EnvironmentVariableTarget.Machine);
1007 if (
string.IsNullOrEmpty(key))
1010 GPALEventType.ERROR,
1011 $
"Environment variable [{envVariableName}] is not set or empty.",
1013 GPALObjectType.None);
1029 _keyFromApiRestClient = restClient;
1041 string key = _keyFromApiRestClient
1042 .WithEndpoint(endpoint)
1045 if (
string.IsNullOrEmpty(key))
1048 GPALEventType.ERROR,
1049 $
"REST API returned no key for endpoint [{endpoint}].",
1051 GPALObjectType.None);
1065 if (serviceAccountKeyFile ==
null ||
string.IsNullOrWhiteSpace(serviceAccountKeyFile.
Filename) || !File.Exists(serviceAccountKeyFile.
Filename))
1068 GPALEventType.ERROR,
1069 $
"Service account key file missing or not found: [{serviceAccountKeyFile?.Filename ?? "<
null>
"}]",
1071 GPALObjectType.None);
1075 ServiceAccountKeyJson = File.ReadAllText(serviceAccountKeyFile.
Filename);
1086 if (
string.IsNullOrWhiteSpace(serviceAccountKey))
1089 GPALEventType.ERROR,
1090 "Service account key JSON string is empty.",
1092 GPALObjectType.None);
1096 ServiceAccountKeyJson = serviceAccountKey;
1107 if (serviceAccountKey ==
null)
1110 GPALEventType.ERROR,
1111 "Service account key object is null.",
1113 GPALObjectType.None);
1119 ServiceAccountKeyJson = System.Text.Json.JsonSerializer.Serialize(serviceAccountKey);
1121 catch (Exception ex)
1124 GPALEventType.EXCEPTION,
1125 $
"Failed to serialize service account key object",
1127 GPALObjectType.None, ex);
1140 if (ServiceType != CredentialServiceType.None)
1142 EnsureValidToken(out
string accessToken);
1158 if (credentials is Credentials masterCredentials)
1160 Username = masterCredentials.Username;
1161 Password = masterCredentials.Password;
1163 ClientId = masterCredentials.ClientId;
1165 ServiceAccountKeyJson = masterCredentials.ServiceAccountKeyJson;
1183 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"Grid cannot be null in SaveTo.",
null, GPALObjectType.None);
1187 var credentials = ServiceType
switch
1189 CredentialServiceType.Bitwarden => BitwardenHandler.Handle(
this),
1190 CredentialServiceType.LastPass => LastPassHandler.Handle(
this),
1191 CredentialServiceType.OnePassword => OnePasswordHandler.Handle(
this),
1192 CredentialServiceType.Dashlane => DashlaneHandler.Handle(
this),
1193 CredentialServiceType.Keeper => KeeperHandler.Handle(
this),
1194 CredentialServiceType.None => DirectHandler.Handle(
this),
1196 _ =>
throw new NotSupportedException(
1197 $
"SaveTo is not supported for CredentialServiceType.{ServiceType}.")
1200 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Saving [{credentials.Count()}] credentials from [{ServiceType}] to grid.",
null, GPALObjectType.None);
1202 foreach (var credential
in credentials)
1204 grid.AddRow(
new List<string> { credential.Username, credential.Password });
1225 switch (ServiceType)
1227 case CredentialServiceType.Google:
1228 if (!
string.IsNullOrEmpty(ServiceAccountKeyJson))
1230 AccessToken = GoogleHandler.FetchServiceAccountToken(
this);
1234 AccessToken = GoogleHandler.FetchAccessToken(
this);
1237 case CredentialServiceType.AWS:
1240 case CredentialServiceType.Azure:
1241 AccessToken = AzureHandler.FetchAccessToken(
this);
1243 case CredentialServiceType.Bitwarden:
1244 AccessToken = BitwardenHandler.FetchAccessToken(
this);
1246 case CredentialServiceType.Dashlane:
1247 AccessToken = DashlaneHandler.FetchAccessToken(
this);
1249 case CredentialServiceType.Keeper:
1250 AccessToken = KeeperHandler.FetchAccessToken(
this);
1252 case CredentialServiceType.LastPass:
1253 AccessToken = LastPassHandler.FetchAccessToken(
this);
1255 case CredentialServiceType.OnePassword:
1256 AccessToken = OnePasswordHandler.FetchAccessToken(
this);
1258 case CredentialServiceType.StaticKey:
1264 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"No access token fetcher for service.",
null, GPALObjectType.None);
1273 private bool IsTokenExpired()
1275 if (
string.IsNullOrEmpty(
AccessToken) || IssueTime == DateTime.MinValue)
return true;
1276 return DateTime.UtcNow > ExpiresAt;
1283 internal class CredentialResult
1288 public string Username {
get; }
1292 public string Password {
get; }
1294 public CredentialResult(
string username,
string password)
1296 Username = username;
1297 Password = password;
1304 internal static class DirectHandler
1311 internal static List<CredentialResult> Handle(Credentials creds)
1313 var results =
new List<CredentialResult>();
1315 if (
string.IsNullOrEmpty(creds.
Username) &&
string.IsNullOrEmpty(creds.Password))
1317 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Username and Password are both empty.",
null, GPALObjectType.None);
1321 results.Add(
new CredentialResult(creds.
Username ??
"", creds.Password ??
""));
1329 internal static class AWSHandler
1336 internal static string FetchAccessToken(Credentials creds)
1338 creds.AccessToken =
"";
1341 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Client ID or Client Secret is missing.",
null, GPALObjectType.None);
1345 string scopeUrl = Credentials.GetScopeValue(creds.
OAuthScope);
1350 var parameters = $
"grant_type=refresh_token&" +
1351 $
"client_id={Uri.EscapeDataString(creds.ClientId)}&" +
1352 $
"client_secret={Uri.EscapeDataString(creds.ClientSecret)}&" +
1353 $
"refresh_token={Uri.EscapeDataString(creds.RefreshToken)}";
1355 var client = GPAL.RESTClient
1358 .WithParameters(parameters);
1359 var response = client.Execute();
1363 var authData = System.Text.Json.JsonSerializer.Deserialize<AWSTokenResponse>(response);
1366 if (authData !=
null)
1368 creds.AccessToken = authData.AccessToken ??
"";
1372 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to refresh AWS access token. Response: [{response}]",
null, GPALObjectType.None);
1374 else if (!
string.IsNullOrEmpty(authData?.RefreshToken))
1381 creds.IssueTime = DateTime.UtcNow;
1382 creds.ExpiresIn = authData.ExpiresIn;
1386 catch (Exception ex)
1388 var errorResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(response);
1389 string error = errorResponse?.ContainsKey(
"error") ==
true ? errorResponse[
"error"] :
"Unknown error";
1390 string errorDescription = errorResponse?.ContainsKey(
"error_description") ==
true ? errorResponse[
"error_description"] :
"No description";
1391 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to refresh AWS access token. Error: [{error}], Description: [{errorDescription}]",
null, GPALObjectType.None, ex);
1396 creds.AuthCode = AuthHandler.GetAuthCode(creds);
1398 if (
string.IsNullOrEmpty(creds.
AuthCode))
1400 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"No authorization code provided.",
null, GPALObjectType.None);
1405 var parameters = $
"grant_type=authorization_code&" +
1406 $
"client_id={Uri.EscapeDataString(creds.ClientId)}&" +
1407 $
"client_secret={Uri.EscapeDataString(creds.ClientSecret)}&" +
1408 $
"code={Uri.EscapeDataString(creds.AuthCode)}&" +
1409 $
"redirect_uri={Uri.EscapeDataString(creds.Config.AWSRedirectUri)}";
1411 var client = GPAL.RESTClient
1414 .WithParameters(parameters);
1415 var response = client.Execute();
1419 var authData = System.Text.Json.JsonSerializer.Deserialize<AWSTokenResponse>(response);
1421 if (authData !=
null)
1423 creds.AccessToken = authData.AccessToken ??
"";
1426 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to fetch AWS access token. Response: [{response}]",
null, GPALObjectType.None);
1428 else if (!
string.IsNullOrEmpty(authData?.RefreshToken))
1435 creds.IssueTime = DateTime.UtcNow;
1436 creds.ExpiresIn = authData.ExpiresIn;
1440 catch (Exception ex)
1442 var errorResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(response);
1443 string error = errorResponse?.ContainsKey(
"error") ==
true ? errorResponse[
"error"] :
"Unknown error";
1444 string errorDescription = errorResponse?.ContainsKey(
"error_description") ==
true ? errorResponse[
"error_description"] :
"No description";
1445 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to fetch AWS access token. Error: [{error}], Description: [{errorDescription}]",
null, GPALObjectType.None, ex);
1456 internal static class AzureHandler
1463 internal static string FetchAccessToken(Credentials creds)
1465 creds.AccessToken =
"";
1468 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Client ID or Client Secret is missing.",
null, GPALObjectType.None);
1472 string scopeUrl = Credentials.GetScopeValue(creds.
OAuthScope);
1477 var parameters = $
"grant_type=refresh_token&" +
1478 $
"client_id={Uri.EscapeDataString(creds.ClientId)}&" +
1479 $
"client_secret={Uri.EscapeDataString(creds.ClientSecret)}&" +
1480 $
"refresh_token={Uri.EscapeDataString(creds.RefreshToken)}&" +
1481 $
"scope={Uri.EscapeDataString(scopeUrl)}";
1483 var client = GPAL.RESTClient
1486 .WithParameters(parameters);
1487 var response = client.Execute();
1491 creds.AzureTokenResponse = System.Text.Json.JsonSerializer.Deserialize<AzureTokenResponse>(response);
1493 if (creds.AzureTokenResponse !=
null)
1495 creds.AccessToken = creds.AzureTokenResponse.AccessToken ??
"";
1499 creds.ExpiresIn = creds.AzureTokenResponse.
ExpiresIn;
1500 creds.IssueTime = DateTime.UtcNow;
1503 if (!
string.IsNullOrEmpty(creds.AzureTokenResponse.
RefreshToken))
1510 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to refresh Azure access token. Response: [{response}]",
null, GPALObjectType.None);
1513 catch (Exception ex)
1515 var errorResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(response);
1516 string error = errorResponse?.ContainsKey(
"error") ==
true ? errorResponse[
"error"] :
"Unknown error";
1517 string errorDescription = errorResponse?.ContainsKey(
"error_description") ==
true ? errorResponse[
"error_description"] :
"No description";
1518 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to refresh Azure access token. Error: [{error}], Description: [{errorDescription}]",
null, GPALObjectType.None, ex);
1523 creds.AuthCode = AuthHandler.GetAuthCode(creds);
1525 if (
string.IsNullOrEmpty(creds.
AuthCode))
1527 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"No authorization code provided.",
null, GPALObjectType.None);
1532 var parameters = $
"grant_type=authorization_code&" +
1533 $
"client_id={Uri.EscapeDataString(creds.ClientId)}&" +
1534 $
"client_secret={Uri.EscapeDataString(creds.ClientSecret)}&" +
1535 $
"code={Uri.EscapeDataString(creds.AuthCode)}&" +
1536 $
"redirect_uri={Uri.EscapeDataString(creds.Config.AzureRedirectUri)}&" +
1537 $
"scope={Uri.EscapeDataString(scopeUrl)}";
1539 var client = GPAL.RESTClient
1542 .WithParameters(parameters);
1543 var response = client.Execute();
1547 creds.AzureTokenResponse = System.Text.Json.JsonSerializer.Deserialize<AzureTokenResponse>(response);
1549 if (creds.AzureTokenResponse !=
null)
1551 creds.AccessToken = creds.AzureTokenResponse.AccessToken ??
"";
1555 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to fetch Azure access token. Response: [{response}]",
null, GPALObjectType.None);
1557 else if (!
string.IsNullOrEmpty(creds.AzureTokenResponse?.
RefreshToken))
1564 creds.ExpiresIn = creds.AzureTokenResponse.
ExpiresIn;
1565 creds.IssueTime = DateTime.UtcNow;
1569 catch (Exception ex)
1571 var errorResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(response);
1572 string error = errorResponse?.ContainsKey(
"error") ==
true ? errorResponse[
"error"] :
"Unknown error";
1573 string errorDescription = errorResponse?.ContainsKey(
"error_description") ==
true ? errorResponse[
"error_description"] :
"No description";
1574 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to fetch Azure access token. Error: [{error}], Description: [{errorDescription}]",
null, GPALObjectType.None, ex);
1586 internal static class GoogleHandler
1593 internal static string FetchAccessToken(Credentials creds)
1595 creds.AccessToken =
"";
1599 string clientId, clientSecret;
1603 var parts = Credentials.SafeSplit(creds.
ServiceKey);
1604 clientId = parts.Item1;
1605 clientSecret = parts.Item2;
1613 var parameters = $
"grant_type=refresh_token&" +
1614 $
"client_id={Uri.EscapeDataString(clientId)}&" +
1615 $
"client_secret={Uri.EscapeDataString(clientSecret)}&" +
1616 $
"refresh_token={Uri.EscapeDataString(creds.RefreshToken)}";
1618 var response = GPAL.RESTClient
1621 .WithParameters(parameters)
1622 .WithEncoding(ContentEncoding.UrlEncoded)
1625 creds.GoogleTokenResponse = System.Text.Json.JsonSerializer.Deserialize<GoogleTokenResponse>(response);
1626 creds.AccessToken = creds.GoogleTokenResponse?.
AccessToken ??
"";
1630 creds.IssueTime = DateTime.UtcNow;
1631 creds.ExpiresIn = creds.GoogleTokenResponse.
ExpiresIn;
1635 GPAL.PublishSimpleEvent(
1636 GPALEventType.ERROR,
1637 $
"Failed to refresh Google access token. Response: [{response}]",
1639 GPALObjectType.None);
1644 creds.AuthCode = AuthHandler.GetAuthCode(creds);
1646 if (
string.IsNullOrEmpty(creds.
AuthCode))
1648 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"No authorization code provided.",
null, GPALObjectType.None);
1652 string redirectUri = creds.Config.UseRestRedirect
1653 ? creds.Config.GoogleRestRedirectUri
1656 var parameters = $
"{creds.Config.GrantTypeKey}=authorization_code&" +
1657 $
"client_id={Uri.EscapeDataString(creds.ClientId)}&" +
1658 $
"client_secret={Uri.EscapeDataString(creds.ClientSecret)}&" +
1659 $
"{creds.Config.GoogleAuthCodeKey}={Uri.EscapeDataString(creds.AuthCode)}&" +
1660 $
"redirect_uri={Uri.EscapeDataString(redirectUri)}";
1662 var response = GPAL.RESTClient
1665 .WithParameters(parameters)
1666 .WithEncoding(ContentEncoding.UrlEncoded)
1669 creds.GoogleTokenResponse = System.Text.Json.JsonSerializer.Deserialize<GoogleTokenResponse>(response);
1670 creds.AccessToken = creds.GoogleTokenResponse?.
AccessToken ??
"";
1674 creds.IssueTime = DateTime.UtcNow;
1675 creds.ExpiresIn = creds.GoogleTokenResponse.
ExpiresIn;
1679 GPAL.PublishSimpleEvent(
1680 GPALEventType.ERROR,
1681 $
"Failed to fetch Google access token. Response: [{response}]",
1683 GPALObjectType.None);
1686 if (!
string.IsNullOrEmpty(creds.GoogleTokenResponse?.
RefreshToken))
1701 public static string Base64UrlEncode(
byte[] input)
1703 return Convert.ToBase64String(input)
1714 private static byte[] Base64UrlDecode(
string input)
1716 string padded = input
1720 switch (padded.Length % 4)
1722 case 2: padded +=
"==";
break;
1723 case 3: padded +=
"=";
break;
1726 return Convert.FromBase64String(padded);
1734 internal static string FetchServiceAccountToken(Credentials creds)
1736 creds.AccessToken =
"";
1738 if (
string.IsNullOrEmpty(creds.ServiceAccountKeyJson))
1740 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Service account key JSON is missing.",
null, GPALObjectType.None);
1746 var serviceAccount = System.Text.Json.JsonSerializer.Deserialize<GoogleServiceAccountKey>(creds.ServiceAccountKeyJson);
1748 var iat = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
1749 var exp = iat + 3600;
1751 var pemReader =
new PemReader(
new StringReader(serviceAccount.PrivateKey));
1752 var keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
1753 var privateKey = (Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters)keyPair.Private;
1755 var header =
new { alg =
"RS256", typ =
"JWT" };
1759 iss = serviceAccount.ClientEmail,
1760 scope = Credentials.GetScopeValue(creds.
OAuthScope),
1765 var headerJson = JsonSerializer.Serialize(header);
1766 var payloadJson = JsonSerializer.Serialize(payload);
1768 var headerBytes = Encoding.UTF8.GetBytes(headerJson);
1769 var payloadBytes = Encoding.UTF8.GetBytes(payloadJson);
1771 string headerBase64Url = Base64UrlEncode(headerBytes);
1772 string payloadBase64Url = Base64UrlEncode(payloadBytes);
1774 string unsignedJwt = $
"{headerBase64Url}.{payloadBase64Url}";
1776 var signer = SignerUtilities.GetSigner(
"SHA256withRSA");
1777 signer.Init(
true, privateKey);
1778 var bytes = Encoding.UTF8.GetBytes(unsignedJwt);
1779 signer.BlockUpdate(bytes, 0, bytes.Length);
1781 var signatureBytes = signer.GenerateSignature();
1782 var signature = Base64UrlEncode(signatureBytes);
1784 var jwt = $
"{unsignedJwt}.{signature}";
1786 var parameters = $
"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={Uri.EscapeDataString(jwt)}";
1788 var response = GPAL.RESTClient
1791 .WithParameters(parameters)
1792 .WithEncoding(ContentEncoding.UrlEncoded)
1793 .WithHttpMethod(
"POST")
1796 var authData = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(response);
1802 creds.IssueTime = DateTime.UtcNow;
1804 if (authData !=
null &&
1805 authData.TryGetValue(
"expires_in", out var expStr) &&
1806 int.TryParse(expStr, out
int expires))
1808 creds.ExpiresIn = expires;
1812 creds.ExpiresIn = 0;
1817 GPAL.PublishSimpleEvent(
1818 GPALEventType.ERROR,
1819 $
"Failed to fetch service account token. Response: [{response}]",
1821 GPALObjectType.None);
1824 catch (Exception ex)
1826 GPAL.PublishSimpleEvent(
1827 GPALEventType.ERROR,
1828 $
"Failed to fetch service account token",
1830 GPALObjectType.None, ex);
1840 internal static class BitwardenHandler
1847 internal static List<CredentialResult> Handle(Credentials creds)
1849 var results =
new List<CredentialResult>();
1851 creds.AccessToken = FetchAccessToken(creds);
1856 var vaultJson = GPAL.RESTClient
1859 .WithParameters(
new Dictionary<string, string>
1866 var vaultData = JsonSerializer.Deserialize<BitwardenVaultResponse>(vaultJson);
1868 if (vaultData?.Data !=
null)
1870 foreach (var item
in vaultData.Data)
1872 if (item.Login !=
null &&
1873 Credentials.MatchesDomain(item.Login.Uris?.Select(u => u.Uri), creds.
Domain))
1874 results.Add(
new CredentialResult(item.Login.Username, item.Login.Password));
1886 internal static string FetchAccessToken(Credentials creds)
1888 var (clientId, clientSecret) = Credentials.SafeSplit(creds.
ServiceKey);
1890 var response = GPAL.RESTClient
1895 grant_type =
"password",
1897 password = creds.Password,
1898 client_id = clientId,
1899 client_secret = clientSecret
1903 creds.BitwardenTokenResponse = JsonSerializer.Deserialize<BitwardenTokenResponse>(response);
1904 creds.AccessToken = creds.BitwardenTokenResponse?.
AccessToken ??
"";
1908 creds.IssueTime = DateTime.UtcNow;
1909 creds.ExpiresIn = creds.BitwardenTokenResponse.
ExpiresIn;
1913 GPAL.PublishSimpleEvent(
1914 GPALEventType.ERROR,
1915 $
"Bitwarden auth failed. Response: [{response}]",
1917 GPALObjectType.None);
1927 internal static class LastPassHandler
1934 internal static List<CredentialResult> Handle(Credentials creds)
1936 var results =
new List<CredentialResult>();
1939 if (!EnsureLoggedIn(creds))
1945 var process =
new Process
1947 StartInfo =
new ProcessStartInfo
1950 Arguments =
"ls --json",
1951 RedirectStandardOutput =
true,
1952 UseShellExecute =
false,
1953 CreateNoWindow =
true
1958 string jsonOutput = process.StandardOutput.ReadToEnd();
1959 process.WaitForExit();
1961 if (process.ExitCode != 0)
1963 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"LastPass CLI returned exit code [{process.ExitCode}].",
null, GPALObjectType.None);
1967 var items = JsonSerializer.Deserialize<List<LastPassItem>>(jsonOutput);
1971 string searchTerm = (creds.Target ??
"").ToLowerInvariant();
1973 foreach (var item
in items)
1975 if (
string.IsNullOrEmpty(item.Username) &&
string.IsNullOrEmpty(item.Password))
1978 bool matches =
string.IsNullOrEmpty(searchTerm) ||
1979 (item.Name?.ToLowerInvariant().Contains(searchTerm) ==
true) ||
1980 (item.Url?.ToLowerInvariant().Contains(searchTerm) ==
true) ||
1981 (item.Username?.ToLowerInvariant().Contains(searchTerm) ==
true);
1983 if (matches && Credentials.MatchesDomain(item.Url, creds.
Domain))
1985 results.Add(
new CredentialResult(item.Username ??
"", item.Password ??
""));
1990 catch (Exception ex)
1992 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"LastPass vault retrieval failed",
null, GPALObjectType.None, ex);
2003 internal static string FetchAccessToken(Credentials creds)
2007 EnsureLoggedIn(creds);
2008 return "LastPass-CLI-Session";
2016 private static bool EnsureLoggedIn(Credentials creds)
2020 var statusProcess =
new Process
2022 StartInfo =
new ProcessStartInfo
2025 Arguments =
"status",
2026 RedirectStandardOutput =
true,
2027 UseShellExecute =
false,
2028 CreateNoWindow =
true
2032 statusProcess.Start();
2033 string statusOutput = statusProcess.StandardOutput.ReadToEnd().Trim();
2034 statusProcess.WaitForExit();
2036 if (statusOutput.ToLower().Contains(
"logged in"))
2040 GPAL.PublishSimpleEvent(
2041 GPALEventType.ERROR,
2042 $
"LastPass CLI is not logged in. Please run this command once on the machine:\n" +
2043 $
"lpass login [{creds.Username}]\n" +
2044 $
"Then re-run your automation.",
2045 null, GPALObjectType.None);
2049 catch (Exception ex)
2051 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2052 $
"LastPass CLI check failed. Is the LastPass CLI (lpass) installed?",
2053 null, GPALObjectType.None, ex);
2062 internal static class OnePasswordHandler
2069 internal static List<CredentialResult> Handle(Credentials creds)
2071 var results =
new List<CredentialResult>();
2074 if (!EnsureSession(creds))
2079 string search = creds.
Target?.Trim() ??
"";
2082 string arguments =
string.IsNullOrEmpty(search)
2083 ?
"item list --format=json"
2084 : $
"item list --search=\"{search}\" --format=json";
2086 var process =
new Process
2088 StartInfo =
new ProcessStartInfo
2091 Arguments = arguments,
2092 RedirectStandardOutput =
true,
2093 UseShellExecute =
false,
2094 CreateNoWindow =
true
2099 string jsonOutput = process.StandardOutput.ReadToEnd();
2100 process.WaitForExit();
2102 if (process.ExitCode != 0)
2104 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2105 $
"1Password CLI returned exit code [{process.ExitCode}]. Make sure you are signed in.",
2106 null, GPALObjectType.None);
2110 var items = JsonSerializer.Deserialize<List<OnePasswordItem>>(jsonOutput);
2114 foreach (var item
in items)
2117 string username =
"";
2118 string password =
"";
2120 foreach (var field
in item.Fields)
2122 if (
string.Equals(field.Label,
"username", StringComparison.OrdinalIgnoreCase) ||
2123 string.Equals(field.Type,
"username", StringComparison.OrdinalIgnoreCase))
2125 username = field.Value ??
"";
2127 else if (
string.Equals(field.Label,
"password", StringComparison.OrdinalIgnoreCase) ||
2128 string.Equals(field.Type,
"password", StringComparison.OrdinalIgnoreCase))
2130 password = field.Value ?? field.Reference ??
"";
2134 if ((!
string.IsNullOrEmpty(username) || !
string.IsNullOrEmpty(password)) &&
2135 Credentials.MatchesDomain(item.Urls?.Select(u => u.Href), creds.
Domain))
2137 results.Add(
new CredentialResult(username, password));
2142 catch (Exception ex)
2144 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2145 $
"1Password vault retrieval failed",
null, GPALObjectType.None, ex);
2156 internal static string FetchAccessToken(Credentials creds)
2159 EnsureSession(creds);
2160 return "1Password-CLI-Session";
2168 private static bool EnsureSession(Credentials creds)
2173 var process =
new Process
2175 StartInfo =
new ProcessStartInfo
2178 Arguments =
"whoami --format=json",
2179 RedirectStandardOutput =
true,
2180 UseShellExecute =
false,
2181 CreateNoWindow =
true
2186 string output = process.StandardOutput.ReadToEnd();
2187 process.WaitForExit();
2189 if (process.ExitCode == 0 && !
string.IsNullOrWhiteSpace(output))
2193 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2194 $
"1Password CLI is not signed in. Please run this once on the target machine:\n" +
2196 $
"Then re-run your automation. (Session lasts ~30 minutes of activity)",
2197 null, GPALObjectType.None);
2201 catch (Exception ex)
2203 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2204 $
"1Password CLI check failed. Is the 1Password CLI (`op`) installed and in PATH?",
2205 null, GPALObjectType.None, ex);
2214 internal static class DashlaneHandler
2221 internal static List<CredentialResult> Handle(Credentials creds)
2223 var results =
new List<CredentialResult>();
2226 if (!EnsureLoggedIn(creds))
2231 string search = creds.
Target?.Trim() ??
"";
2234 string arguments =
string.IsNullOrEmpty(search)
2235 ?
"password list --json"
2236 : $
"password list --search \"{search}\" --json";
2238 var process =
new Process
2240 StartInfo =
new ProcessStartInfo
2243 Arguments = arguments,
2244 RedirectStandardOutput =
true,
2245 RedirectStandardError =
true,
2246 UseShellExecute =
false,
2247 CreateNoWindow =
true
2252 string jsonOutput = process.StandardOutput.ReadToEnd();
2253 string errorOutput = process.StandardError.ReadToEnd();
2254 process.WaitForExit();
2256 if (process.ExitCode != 0)
2258 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2259 $
"Dashlane CLI failed (exit code [{process.ExitCode}]). Error: [{errorOutput.Trim()}]",
2260 null, GPALObjectType.None);
2264 var items = JsonSerializer.Deserialize<List<DashlaneItem>>(jsonOutput);
2268 foreach (var item
in items)
2270 if (!
string.IsNullOrEmpty(item.Username) || !
string.IsNullOrEmpty(item.Password))
2272 if (Credentials.MatchesDomain(item.Url, creds.
Domain))
2273 results.Add(
new CredentialResult(item.Username ??
"", item.Password ??
""));
2278 catch (Exception ex)
2280 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2281 $
"Dashlane vault retrieval failed",
null, GPALObjectType.None, ex);
2292 internal static string FetchAccessToken(Credentials creds)
2295 EnsureLoggedIn(creds);
2296 return "Dashlane-CLI-Session";
2304 private static bool EnsureLoggedIn(Credentials creds)
2308 var process =
new Process
2310 StartInfo =
new ProcessStartInfo
2313 Arguments =
"status --json",
2314 RedirectStandardOutput =
true,
2315 UseShellExecute =
false,
2316 CreateNoWindow =
true
2321 string output = process.StandardOutput.ReadToEnd();
2322 process.WaitForExit();
2324 if (process.ExitCode == 0 && output.ToLower().Contains(
"\"logged_in\":true"))
2328 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2329 $
"Dashlane CLI is not logged in. Please run this once on the target machine:\n" +
2331 $
"Then re-run your automation.",
2332 null, GPALObjectType.None);
2336 catch (Exception ex)
2338 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2339 $
"Dashlane CLI check failed. Is the Dashlane CLI (`dcli`) installed and in PATH?",
2340 null, GPALObjectType.None, ex);
2349 internal static class KeeperHandler
2356 internal static List<CredentialResult> Handle(Credentials creds)
2358 var results =
new List<CredentialResult>();
2360 if (!EnsureLoggedIn(creds))
2365 string search = creds.
Target?.Trim() ??
"";
2368 string arguments =
string.IsNullOrEmpty(search)
2369 ?
"list --format=json"
2370 : $
"search \"{search}\" --format=json";
2372 var process =
new Process
2374 StartInfo =
new ProcessStartInfo
2376 FileName =
"keeper",
2377 Arguments = arguments,
2378 RedirectStandardOutput =
true,
2379 RedirectStandardError =
true,
2380 UseShellExecute =
false,
2381 CreateNoWindow =
true
2386 string jsonOutput = process.StandardOutput.ReadToEnd();
2387 string errorOutput = process.StandardError.ReadToEnd();
2388 process.WaitForExit();
2390 if (process.ExitCode != 0)
2392 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2393 $
"Keeper Commander returned exit code [{process.ExitCode}]. Error: [{errorOutput.Trim()}]",
2394 null, GPALObjectType.None);
2398 var records = JsonSerializer.Deserialize<List<KeeperRecord>>(jsonOutput);
2400 if (records !=
null)
2402 foreach (var record
in records)
2404 string username = record.Login?.Username ??
"";
2405 string password = record.Login?.Password ??
"";
2407 if ((!
string.IsNullOrEmpty(username) || !
string.IsNullOrEmpty(password)) &&
2408 Credentials.MatchesDomain(record.Login?.Url, creds.
Domain))
2410 results.Add(
new CredentialResult(username, password));
2415 catch (Exception ex)
2417 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2418 $
"Keeper vault retrieval failed",
null, GPALObjectType.None, ex);
2429 internal static string FetchAccessToken(Credentials creds)
2432 EnsureLoggedIn(creds);
2433 return "Keeper-Commander-Session";
2441 private static bool EnsureLoggedIn(Credentials creds)
2445 var process =
new Process
2447 StartInfo =
new ProcessStartInfo
2449 FileName =
"keeper",
2450 Arguments =
"whoami --format=json",
2451 RedirectStandardOutput =
true,
2452 UseShellExecute =
false,
2453 CreateNoWindow =
true
2458 string output = process.StandardOutput.ReadToEnd();
2459 process.WaitForExit();
2461 if (process.ExitCode == 0 && !
string.IsNullOrWhiteSpace(output))
2464 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2465 $
"Keeper Commander is not logged in. Please run this once on the target machine:\n" +
2467 $
"Then login with your email / master password (or use 'keeper login').\n" +
2468 $
"After login, you can use 'this-device' for persistent sessions.",
2469 null, GPALObjectType.None);
2473 catch (Exception ex)
2475 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2476 $
"Keeper Commander check failed. Is Keeper Commander (`keeper`) installed and in PATH?",
2477 null, GPALObjectType.None, ex);
2488 internal static class LocalAuthCallbackServer
2490 private static readonly
object _lock =
new object();
2491 private static HttpListener _listener;
2492 private static string _pendingAuthCode;
2493 private static string _pendingAuthScope;
2500 internal static bool StartIfNeeded(
string baseUrl)
2504 if (
null != _listener)
2509 var listener =
new HttpListener();
2510 listener.Prefixes.Add(baseUrl);
2512 _listener = listener;
2514 new Thread(() => Listen(listener)) { IsBackground =
true }.Start();
2517 catch (HttpListenerException)
2526 private static void Listen(HttpListener listener)
2530 while (listener.IsListening)
2532 HttpListenerContext context = listener.GetContext();
2533 HandleRequest(context);
2542 private static void HandleRequest(HttpListenerContext context)
2544 HttpListenerResponse response = context.Response;
2545 string path = context.Request.Url.AbsolutePath.Trim(
'/');
2550 case "access-token":
2551 NameValueCollection query = ParseQueryString(context.Request.Url.Query);
2555 _pendingAuthCode = query[
"code"];
2556 _pendingAuthScope = query[
"scope"];
2559 SendResponse(response,
"{\"message\":\"Authorization code received. You can now call /get-access-token.\"}");
2562 case "get-access-token":
2567 code = _pendingAuthCode ??
string.Empty;
2568 scope = _pendingAuthScope ??
string.Empty;
2571 _pendingAuthCode =
null;
2572 _pendingAuthScope =
null;
2575 json = System.Text.Json.JsonSerializer.Serialize(
new { code, scope });
2576 SendResponse(response, json);
2580 SendResponse(response,
"{}");
2585 private static NameValueCollection ParseQueryString(
string query)
2587 var result =
new NameValueCollection();
2589 if (
string.IsNullOrEmpty(query))
2592 foreach (
string pair
in query.TrimStart(
'?').Split(
'&'))
2594 if (
string.IsNullOrEmpty(pair))
2597 string[] parts = pair.Split(
new[] {
'=' }, 2);
2598 string key = Uri.UnescapeDataString(parts[0]);
2599 string value = parts.Length > 1 ? Uri.UnescapeDataString(parts[1]) :
string.Empty;
2600 result[key] = value;
2606 private static void SendResponse(HttpListenerResponse response,
string message)
2610 response.StatusCode = (int)HttpStatusCode.OK;
2611 response.ContentType =
"application/json";
2612 response.ContentEncoding = Encoding.UTF8;
2614 byte[] buffer = Encoding.UTF8.GetBytes(message ??
"");
2615 response.OutputStream.Write(buffer, 0, buffer.Length);
2620 try { response.Close(); }
catch { }
2627 internal static void Stop()
2631 if (
null == _listener)
2634 try { _listener.Stop(); _listener.Close(); }
catch { }
2636 _pendingAuthCode =
null;
2637 _pendingAuthScope =
null;
2645 internal static class AuthHandler
2648 internal const string DefaultTokenUrl =
"http://localhost:3000/";
2656 internal static string GetAuthCodeFromRest(Process browserProcess,
int timeoutInSeconds)
2658 string authCode =
"";
2665 var restClient = GPAL.RESTClient
2666 .WithAPIBase(GPAL.OAuthTokenUrl)
2667 .WithEndpoint(
"get-access-token");
2670 TimeSpan timeout = TimeSpan.FromSeconds(timeoutInSeconds);
2671 TimeSpan interval = TimeSpan.FromSeconds(1);
2672 DateTime start = DateTime.UtcNow;
2674 while ((DateTime.UtcNow - start) < timeout)
2676 AuthCodePayload tokenInfo = restClient.Execute<AuthCodePayload>();
2677 if (!
string.IsNullOrEmpty(tokenInfo?.Code))
2679 authCode = tokenInfo.
Code;
2680 GPAL.PublishSimpleEvent(
2682 "Authorization code retrieved from REST server.",
2689 Thread.Sleep(interval);
2692 if (
string.IsNullOrEmpty(authCode))
2693 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Timeout waiting for authorization code from REST server.",
null, GPALObjectType.None);
2695 catch (Exception ex)
2697 GPAL.PublishSimpleEvent(
2698 GPALEventType.ERROR,
2699 $
"REST redirect auth failed",
2704 if (
null != browserProcess)
2705 BrowserHelper.KillProcess(browserProcess);
2731 internal static bool ResolveTokenUrl(Credentials creds)
2733 bool retVal =
false;
2736 GPAL.OAuthTokenUrl =
true ==
string.IsNullOrWhiteSpace(configured)
2738 : $
"{new Uri(configured).GetLeftPart(UriPartial.Authority)}/";
2740 if (
true == LocalAuthCallbackServer.StartIfNeeded(GPAL.OAuthTokenUrl))
2744 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Listening on [{GPAL.OAuthTokenUrl}] to catch the token.", creds, GPALObjectType.Other);
2746 else if (
true == Browser.BrowserHelper.RestApiAnswering(GPAL.OAuthTokenUrl))
2748 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"The GPALRestAPI already on [{GPAL.OAuthTokenUrl}] will catch the token.", creds, GPALObjectType.Other);
2754 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Something that is not GPALRestAPI is on [{GPAL.OAuthTokenUrl}], and the redirect registered with the provider cannot point anywhere else", creds, GPALObjectType.Other);
2760 internal static string GetAuthCode(Credentials creds)
2762 string authCode =
"";
2764 Process browserProcess =
null;
2765 bool ownsLocalServer =
false;
2767 bool publishToConsole = GPAL.GPALSettings.PublishToConsole;
2769 GPALEventType consoleEvents = GPAL.GPALSettings.ConsoleEvents;
2771 if (
false == publishToConsole)
2772 GPAL.WithPublishToConsole();
2773 else if (
false == consoleEvents.HasFlag(GPALEventType.INFO))
2774 GPAL.ConsoleEvents |= GPALEventType.INFO;
2784 ownsLocalServer = ResolveTokenUrl(creds);
2786 string authUrl = AuthHandler.GetAuthUrl(creds);
2788 browserProcess = creds.Browser.
MagicHelper.LaunchBrowser(creds.Browser, authUrl);
2789 authCode = GetAuthCodeFromRest(browserProcess, 60);
2793 if (
string.IsNullOrEmpty(authCode))
2795 string authUrl = AuthHandler.GetAuthUrl(creds);
2798 GPAL.PublishSimpleEvent(
2799 GPALEventType.WARNING,
2800 "No authorization code arrived via the local redirect listener. Falling back to manual entry.",
2808 GPAL.PublishSimpleEvent(
2810 $
"Open this URL in a browser, authorize, and provide the code: \n[{authUrl}]",
2815 Console.WriteLine(
"\nEnter auth code: ");
2816 authCode = Console.ReadLine();
2818 if (
string.IsNullOrEmpty(authCode))
2820 GPAL.PublishSimpleEvent(
2821 GPALEventType.ERROR,
2822 "No authorization code provided.",
2835 if (ownsLocalServer)
2836 LocalAuthCallbackServer.Stop();
2838 if (
false == publishToConsole)
2839 GPAL.WithPublishToConsole(GPALEventType.NONE);
2840 else if (
false == consoleEvents.HasFlag(GPALEventType.INFO))
2841 GPAL.ConsoleEvents ^= GPALEventType.INFO;
2851 internal static string GetAuthUrl(Credentials creds)
2853 switch (creds.ServiceType)
2855 case CredentialServiceType.Google:
2856 return $
"{creds.Config.GoogleConsentBase}/o/oauth2/v2/auth" +
2857 $
"?client_id={Uri.EscapeDataString(creds.ClientId)}" +
2858 $
"&redirect_uri={Uri.EscapeDataString(creds.Config.UseRestRedirect ? creds.Config.GoogleRestRedirectUri : creds.Config.GoogleRedirectUri)}" +
2859 $
"&response_type=code" +
2860 $
"&scope={Uri.EscapeDataString(Credentials.GetScopeValue(creds.OAuthScope))}" +
2861 $
"&access_type=offline";
2863 case CredentialServiceType.Azure:
2864 return $
"{creds.Config.AzureAuthBase}/oauth2/v2.0/authorize" +
2865 $
"?client_id={Uri.EscapeDataString(creds.ClientId)}" +
2866 $
"&response_type=code" +
2867 $
"&redirect_uri={Uri.EscapeDataString(creds.Config.AzureRedirectUri)}" +
2868 $
"&response_mode=query" +
2869 $
"&scope={Uri.EscapeDataString(Credentials.GetScopeValue(creds.OAuthScope))}";
2871 case CredentialServiceType.AWS:
2872 return $
"{creds.Config.AWSAuthBase}/oauth2/authorize" +
2873 $
"?client_id={Uri.EscapeDataString(creds.ClientId)}" +
2874 $
"&response_type=code" +
2875 $
"&redirect_uri={Uri.EscapeDataString(creds.Config.AWSRedirectUri)}" +
2876 $
"&scope={Uri.EscapeDataString(Credentials.GetScopeValue(creds.OAuthScope))}";
2878 case CredentialServiceType.Bitwarden:
2879 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"Bitwarden OAuth not implemented.", creds, GPALObjectType.Other);
JSON response from the AWS Cognito OAuth token endpoint.
string TokenType
OAuth token type, normally "Bearer".
string RefreshToken
Token used to obtain a new access token once it expires. Cognito may not always return one.
string AccessToken
Bearer access token for calling AWS APIs.
int ExpiresIn
Number of seconds until AccessToken expires.
string IdToken
OpenID Connect ID token containing identity claims for the authenticated user.
Authorization code (and granted scope) returned by GPALRESTAPI's get-access-token endpoint after a us...
string Code
OAuth authorization code to exchange for an access token.
string Scope
OAuth scope(s) granted along with the authorization code.
JSON response from the Azure AD (Microsoft Entra ID) OAuth token endpoint.
string RefreshToken
Token used to obtain a new access token once it expires.
string Scope
Space-delimited list of scopes granted to AccessToken.
string AccessToken
Bearer access token for calling Microsoft Graph or other Azure APIs.
string TokenType
OAuth token type, normally "Bearer".
int ExpiresIn
Number of seconds until AccessToken expires.
JSON response from Bitwarden's OAuth token endpoint.
string RefreshToken
Token used to obtain a new access token once it expires.
int ExpiresIn
Number of seconds until AccessToken expires.
string AccessToken
Bearer access token used to call the Bitwarden vault API.
string TokenType
OAuth token type, normally "Bearer".
Browser object that contains the fluent methods to create your Browser workflow. Instatiated using G...
MagicHelper MagicHelper
The MagicHelper used to issue commands when this Browser is using the OttoMagic engine.
Endpoint URLs, redirect URIs, and protocol field names used by the credential service handlers....
string GrantTypeValue
Default OAuth grant_type value (resource owner password credentials).
string AzureAuthEndpoint
Path of the Azure AD OAuth token endpoint, appended to AzureAuthBase.
string AWSAuthBase
Base URL of your AWS Cognito hosted UI domain. Must be replaced with your actual Cognito domain.
bool UseRestRedirect
When true, the OAuth authorization code is retrieved by polling GPALRESTAPI's get-access-token endpoi...
string AccessTokenKey
JSON field key under which an OAuth token response returns the access token.
string PasswordKey
Form field key for the password parameter.
string GoogleAuthCodeKey
Query string key under which Google returns the authorization code.
string AWSRedirectUri
Redirect URI used for the manual (out-of-band) AWS Cognito OAuth flow.
string ClientIdKey
Form field key for the OAuth client_id parameter.
static void Save(GPALFile file=null)
Saves the default credentials configuration to the config file as a starter file for editing....
string SearchKey
Query parameter key used when searching a vault for an item.
string BitwardenVaultBase
Base URL for the Bitwarden public vault API.
string GoogleAuthEndpoint
Path of Google's OAuth token endpoint, appended to GoogleAuthBase.
string GoogleRedirectUri
Redirect URI used for the manual (out-of-band) Google OAuth flow.
string BitwardenAuthBase
Base URL for the Bitwarden identity (token) service.
string GoogleAuthBase
Base URL for Google's OAuth token endpoint.
string AuthorizationHeader
HTTP header name used to send the bearer access token.
string ClientSecretKey
Form field key for the OAuth client_secret parameter.
string GoogleRestRedirectUri
Redirect URI used when UseRestRedirect is true; GPALRESTAPI listens here for the OAuth callback.
string BitwardenVaultEndpoint
Path of the Bitwarden vault items endpoint, appended to BitwardenVaultBase.
string AzureRedirectUri
Redirect URI used for the manual (out-of-band) Azure OAuth flow.
string BitwardenAuthEndpoint
Path of the Bitwarden OAuth token endpoint, appended to BitwardenAuthBase.
static CredentialsConfig Load(GPALFile file=null)
Loads the credentials config, falling back to defaults if the file is missing or invalid....
string GoogleConsentBase
Base URL for Google's OAuth consent/authorization page.
string AWSAuthEndpoint
Path of the AWS Cognito OAuth token endpoint, appended to AWSAuthBase.
string GrantTypeKey
Form field key for the OAuth grant_type parameter.
string AzureAuthBase
Base URL for the Azure AD (Microsoft Entra ID) OAuth token endpoint.
string UsernameKey
Form field key for the username parameter.
string GoogleServiceAccountTokenUri
Token endpoint used when exchanging a Google service-account JWT for an access token.
ICredentials ToGPALObject()
Fluent interface to return this credentials object as an ICredentials.
IAllowCredentialsServiceKeyWithCredentials WithAuthRedirectUrl(string redirectUrl)
Where the OAuth redirect lands, and therefore what has to catch the token. This is the one setting th...
string ServiceKey
Combined "ClientId:ClientSecret" service key supplied via .WithServiceKey or derived from ....
string RefreshToken
OAuth refresh token supplied via .WithRefreshToken, or returned by a token exchange and used to renew...
IAllowCredentialsServiceKeyWithCredentials WithAccessToken(string accessToken)
A token the workflow already holds, for the schemes that present one: Bearer, X-Auth-Token,...
IAllowCredentialsIntoCredentials WithServiceAccountKey(GoogleServiceAccountKey serviceAccountKey)
Supplies a Google Cloud service account key as a deserialized object, used to fetch an access token w...
CredentialsConfig Config
Endpoint/protocol configuration for the credential service handlers, loaded from ....
OAuthScope OAuthScope
OAuth scope requested via .WithScope when fetching an access token.
string Username
Username supplied via .WithUsername, or the username field returned from a vault item.
IAllowCredentialsIntoCredentials WithServiceAccountKey(GPALFile serviceAccountKeyFile)
Loads a Google Cloud service account key from a JSON key file, used to fetch an access token without ...
IAllowKeyFromApiEndpoint WithKeyFromApi(IRESTClient restClient)
Like WithServiceKey, but fetches the key via a GET request made with the supplied,...
IAllowCredentialsIntoCredentials WithDomain(string domain)
Restricts GetCredentialsFor/SaveTo results to vault items whose URL contains this domain....
IAllowCredentialsIntoCredentials WithRefreshToken(string refreshToken)
Supplies a previously-obtained OAuth refresh token, used to fetch a new access token without re-autho...
IAllowCredentialsIntoCredentials WithClientSecret(string clientSecret)
Sets the OAuth client secret and derives ServiceKey as "ClientId:ClientSecret".
IAllowCredentialsIntoCredentials GetCredentialsFor(string target)
Sets the search term used to find a matching item in the target password manager (e....
string Domain
Optional URL filter supplied via .WithDomain, used to narrow vault matches down to a specific site.
string ClientId
OAuth client ID supplied via .WithClientId or derived from .WithServiceKey.
IAllowCredentialsIntoCredentials WithServiceAccountKey(string serviceAccountKey)
Supplies a Google Cloud service account key as a raw JSON string, used to fetch an access token witho...
IAllowCredentialsServiceKeyWithCredentials WithWebAuth(WebAuthType webAuthType)
How the site expects to be told who you are. A browser handed this credential acts on it accordingly,...
IAllowClientSecret WithClientId(string clientId)
Sets the OAuth client ID used to authenticate to ServiceType.
string AuthCode
OAuth authorization code supplied via .WithAuthCode, or retrieved automatically during the OAuth redi...
IAllowCredentialsIntoCredentials WithCredentials(ICredentials credentials)
Supplies the login (master) credentials used to authenticate to the credential service itself (e....
IAllowCredentialsIntoCredentials WithServiceKey(string key)
Service key is a custom string consisting of your ClientID:ClientSecret.
IAllowCredentialsIntoCredentials WithScope(OAuthScope scope)
Sets the OAuth scope to request when an access token is fetched.
string AccessToken
Most recently fetched access token for ServiceType, set by FetchAccessToken/GetCredentialsFor.
IAllowCredentialsIntoCredentials WithEndpoint(string endpoint)
Completes .WithKeyFromApi(restClient) by calling the given endpoint on that RESTClient and using the ...
string ClientSecret
OAuth client secret supplied via .WithClientSecret or derived from .WithServiceKey.
IAllowCredentialsUsername WithService(CredentialServiceType serviceType)
Selects which password manager or cloud identity service this credentials request targets.
IAllowCredentialsServiceKeyWithCredentials WithPassword(string password)
Sets the password, either as a credential to retrieve directly (with CredentialServiceType....
IAllowCredentialsIntoCredentials WithAuthCode(string authCode)
Supplies a previously-obtained OAuth authorization code, skipping the interactive authorization step.
WebAuthType WebAuthType
How the site expects to be told who you are, supplied via .WithWebAuth. Read by a browser this creden...
string Target
Search term supplied via GetCredentialsFor, used by the password manager handlers to find a matching ...
void FetchAccessToken(out string accessToken)
Fetches a new access token for ServiceType using the credentials/configuration supplied so far,...
IAllowCredentialsIntoCredentials WithKeyFromEnv(string envVariableName)
Like WithServiceKey, but reads the key from an environment variable (checking process,...
IAllowGetCredentials SaveTo(IGPALGrid< string > grid)
Looks up the credential matching .GetCredentialsFor's target (filtered by .WithDomain if set) and app...
IAllowCredentialsPassword WithUsername(string username)
Sets the username, either as a credential to retrieve directly (with CredentialServiceType....
A single vault item returned by the Dashlane CLI (dcli password list --json).
string Username
Stored username/login.
string Title
Display title of the vault item.
string Id
Unique identifier of the vault item.
string Url
URL associated with the vault item, used for matching against .WithDomain.
string Notes
Free-form notes attached to the vault item.
string Otp
One-time-password/TOTP secret, if configured for the item.
string Password
Stored password.
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
string Filename
We have only one file, accessing it.
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
static IAllowConverterInput Converter
New GPAL Convertor.
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
static IAllowFileName File
Instantiates a new fluent File SETTINGS object. This data object defines settings for use by the ....
Deserialized contents of a Google Cloud service account JSON key file, used by GoogleHandler....
string AuthUri
Google OAuth authorization endpoint URL.
string AuthProviderX509CertUrl
URL of Google's X.509 certificate provider for verifying tokens.
string PrivateKeyId
Identifier of the private key within the service account.
string TokenUri
Google OAuth token endpoint URL used to exchange the signed JWT for an access token.
string ClientId
OAuth client ID associated with the service account.
string ClientEmail
Email address of the service account; used as the JWT issuer (iss claim).
string ClientX509CertUrl
URL of this service account's X.509 certificate.
string PrivateKey
PEM-encoded RSA private key used to sign the JWT assertion.
string ProjectId
Google Cloud project ID that owns this service account.
string Type
Key type, normally "service_account".
JSON response from Google's OAuth token endpoint.
string TokenType
OAuth token type, normally "Bearer".
string AccessToken
Bearer access token for calling Google APIs.
int ExpiresIn
Number of seconds until AccessToken expires.
string RefreshToken
Token used to obtain a new access token once it expires. Only returned on the initial authorization c...
Login fields (username, password, and URL) of a Keeper record.
string Url
URL associated with the login, used for matching against .WithDomain.
string Password
Stored password.
string Username
Stored username/login.
A single record returned by Keeper Commander (keeper list/keeper search --format=json).
KeeperLoginData Login
Login fields (username, password, URL) of the record, if it is a login record.
string Notes
Free-form notes attached to the record.
string RecordType
Keeper record type (e.g. "login").
string Title
Display title of the record.
string RecordUid
Unique identifier of the Keeper record.
A single vault item returned by the LastPass CLI (lpass ls --json).
string Username
Stored username/login.
string Id
Unique identifier of the vault item.
string Password
Stored password.
string Url
URL associated with the vault item, used for matching against .WithDomain.
string Notes
Free-form notes attached to the vault item.
string Name
Display name of the vault item.
A single custom field of a 1Password item, such as a username or password field.
string Value
Field value, when not a secret reference.
string Label
Display label of the field (e.g. "username", "password").
string Reference
secret reference URI (op://...) used to resolve the value for secret fields.
string Type
Field type as reported by the 1Password CLI (e.g. "STRING", "CONCEALED").
string Id
Unique identifier of the field within the item.
A single vault item returned by the 1Password CLI (op item list --format=json).
List< OnePasswordUrl > Urls
URLs associated with this item, used for matching against .WithDomain.
string Id
Unique identifier of the vault item.
string Title
Display title of the vault item.
List< OnePasswordField > Fields
Custom fields (e.g. username, password) belonging to this item.
string Vault
Name or ID of the 1Password vault containing this item.
A single URL associated with a 1Password item.
string Href
The URL string.