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))
182 .WithInput((
GPALFile)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";
812 return "https://www.googleapis.com/auth/youtube.upload";
814 return "https://www.googleapis.com/auth/youtube";
818 return "https://graph.microsoft.com/User.Read";
820 return "https://graph.microsoft.com/Mail.Read";
822 return "https://storage.azure.com/user_impersonation";
824 return "https://management.azure.com/user_impersonation";
835 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Unsupported scope: [{scope}]", scope, GPALObjectType.Other);
887 ServiceType = serviceType;
936 Config.GoogleRestRedirectUri = redirectUrl;
993 var parts = SafeSplit(key);
1007 string key = Environment.GetEnvironmentVariable(envVariableName, EnvironmentVariableTarget.Process)
1008 ?? Environment.GetEnvironmentVariable(envVariableName, EnvironmentVariableTarget.User)
1009 ?? Environment.GetEnvironmentVariable(envVariableName, EnvironmentVariableTarget.Machine);
1011 if (
string.IsNullOrEmpty(key))
1014 GPALEventType.ERROR,
1015 $
"Environment variable [{envVariableName}] is not set or empty.",
1017 GPALObjectType.None);
1033 _keyFromApiRestClient = restClient;
1045 string key = _keyFromApiRestClient
1046 .WithEndpoint(endpoint)
1049 if (
string.IsNullOrEmpty(key))
1052 GPALEventType.ERROR,
1053 $
"REST API returned no key for endpoint [{endpoint}].",
1055 GPALObjectType.None);
1069 if (serviceAccountKeyFile ==
null ||
string.IsNullOrWhiteSpace(serviceAccountKeyFile.
Filename) || !File.Exists(serviceAccountKeyFile.
Filename))
1072 GPALEventType.ERROR,
1073 $
"Service account key file missing or not found: [{serviceAccountKeyFile?.Filename ?? "<
null>
"}]",
1075 GPALObjectType.None);
1079 ServiceAccountKeyJson = File.ReadAllText(serviceAccountKeyFile.
Filename);
1090 if (
string.IsNullOrWhiteSpace(serviceAccountKey))
1093 GPALEventType.ERROR,
1094 "Service account key JSON string is empty.",
1096 GPALObjectType.None);
1100 ServiceAccountKeyJson = serviceAccountKey;
1111 if (serviceAccountKey ==
null)
1114 GPALEventType.ERROR,
1115 "Service account key object is null.",
1117 GPALObjectType.None);
1123 ServiceAccountKeyJson = System.Text.Json.JsonSerializer.Serialize(serviceAccountKey);
1125 catch (Exception ex)
1128 GPALEventType.EXCEPTION,
1129 $
"Failed to serialize service account key object",
1131 GPALObjectType.None, ex);
1150 if (masterCredentials is Credentials master)
1153 Password = master.Password;
1157 ServiceAccountKeyJson = master.ServiceAccountKeyJson;
1174 if (ServiceType != CredentialServiceType.None)
1176 EnsureValidToken(out
string accessToken);
1191 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"Grid cannot be null in SaveTo.",
null, GPALObjectType.None);
1195 var credentials = ServiceType
switch
1197 CredentialServiceType.Bitwarden => BitwardenHandler.Handle(
this),
1198 CredentialServiceType.LastPass => LastPassHandler.Handle(
this),
1199 CredentialServiceType.OnePassword => OnePasswordHandler.Handle(
this),
1200 CredentialServiceType.Dashlane => DashlaneHandler.Handle(
this),
1201 CredentialServiceType.Keeper => KeeperHandler.Handle(
this),
1202 CredentialServiceType.None => DirectHandler.Handle(
this),
1204 _ =>
throw new NotSupportedException(
1205 $
"SaveTo is not supported for CredentialServiceType.{ServiceType}.")
1208 GPAL.
PublishSimpleEvent(GPALEventType.INFO, $
"Saving [{credentials.Count()}] credentials from [{ServiceType}] to grid.",
null, GPALObjectType.None);
1210 foreach (var credential
in credentials)
1212 grid.AddRow(
new List<string> { credential.Username, credential.Password });
1233 switch (ServiceType)
1235 case CredentialServiceType.Google:
1236 if (!
string.IsNullOrEmpty(ServiceAccountKeyJson))
1238 AccessToken = GoogleHandler.FetchServiceAccountToken(
this);
1242 AccessToken = GoogleHandler.FetchAccessToken(
this);
1245 case CredentialServiceType.AWS:
1248 case CredentialServiceType.Azure:
1249 AccessToken = AzureHandler.FetchAccessToken(
this);
1251 case CredentialServiceType.Bitwarden:
1252 AccessToken = BitwardenHandler.FetchAccessToken(
this);
1254 case CredentialServiceType.Dashlane:
1255 AccessToken = DashlaneHandler.FetchAccessToken(
this);
1257 case CredentialServiceType.Keeper:
1258 AccessToken = KeeperHandler.FetchAccessToken(
this);
1260 case CredentialServiceType.LastPass:
1261 AccessToken = LastPassHandler.FetchAccessToken(
this);
1263 case CredentialServiceType.OnePassword:
1264 AccessToken = OnePasswordHandler.FetchAccessToken(
this);
1266 case CredentialServiceType.StaticKey:
1272 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"No access token fetcher for service.",
null, GPALObjectType.None);
1281 private bool IsTokenExpired()
1283 if (
string.IsNullOrEmpty(
AccessToken) || IssueTime == DateTime.MinValue)
return true;
1284 return DateTime.UtcNow > ExpiresAt;
1291 internal class CredentialResult
1296 public string Username {
get; }
1300 public string Password {
get; }
1302 public CredentialResult(
string username,
string password)
1304 Username = username;
1305 Password = password;
1312 internal static class DirectHandler
1319 internal static List<CredentialResult> Handle(Credentials creds)
1321 var results =
new List<CredentialResult>();
1323 if (
string.IsNullOrEmpty(creds.
Username) &&
string.IsNullOrEmpty(creds.Password))
1325 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Username and Password are both empty.",
null, GPALObjectType.None);
1329 results.Add(
new CredentialResult(creds.
Username ??
"", creds.Password ??
""));
1337 internal static class AWSHandler
1344 internal static string FetchAccessToken(Credentials creds)
1346 creds.AccessToken =
"";
1349 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Client ID or Client Secret is missing.",
null, GPALObjectType.None);
1353 string scopeUrl = Credentials.GetScopeValue(creds.
OAuthScope);
1358 var parameters = $
"grant_type=refresh_token&" +
1359 $
"client_id={Uri.EscapeDataString(creds.ClientId)}&" +
1360 $
"client_secret={Uri.EscapeDataString(creds.ClientSecret)}&" +
1361 $
"refresh_token={Uri.EscapeDataString(creds.RefreshToken)}";
1363 var client = GPAL.RESTClient
1366 .WithParameters(parameters);
1367 var response = client.Execute();
1371 var authData = System.Text.Json.JsonSerializer.Deserialize<AWSTokenResponse>(response);
1374 if (authData !=
null)
1376 creds.AccessToken = authData.AccessToken ??
"";
1380 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to refresh AWS access token. Response: [{response}]",
null, GPALObjectType.None);
1382 else if (!
string.IsNullOrEmpty(authData?.RefreshToken))
1389 creds.IssueTime = DateTime.UtcNow;
1390 creds.ExpiresIn = authData.ExpiresIn;
1394 catch (Exception ex)
1396 var errorResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(response);
1397 string error = errorResponse?.ContainsKey(
"error") ==
true ? errorResponse[
"error"] :
"Unknown error";
1398 string errorDescription = errorResponse?.ContainsKey(
"error_description") ==
true ? errorResponse[
"error_description"] :
"No description";
1399 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to refresh AWS access token. Error: [{error}], Description: [{errorDescription}]",
null, GPALObjectType.None, ex);
1404 creds.AuthCode = AuthHandler.GetAuthCode(creds);
1406 if (
string.IsNullOrEmpty(creds.
AuthCode))
1408 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"No authorization code provided.",
null, GPALObjectType.None);
1413 var parameters = $
"grant_type=authorization_code&" +
1414 $
"client_id={Uri.EscapeDataString(creds.ClientId)}&" +
1415 $
"client_secret={Uri.EscapeDataString(creds.ClientSecret)}&" +
1416 $
"code={Uri.EscapeDataString(creds.AuthCode)}&" +
1417 $
"redirect_uri={Uri.EscapeDataString(creds.Config.AWSRedirectUri)}";
1419 var client = GPAL.RESTClient
1422 .WithParameters(parameters);
1423 var response = client.Execute();
1427 var authData = System.Text.Json.JsonSerializer.Deserialize<AWSTokenResponse>(response);
1429 if (authData !=
null)
1431 creds.AccessToken = authData.AccessToken ??
"";
1434 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to fetch AWS access token. Response: [{response}]",
null, GPALObjectType.None);
1436 else if (!
string.IsNullOrEmpty(authData?.RefreshToken))
1443 creds.IssueTime = DateTime.UtcNow;
1444 creds.ExpiresIn = authData.ExpiresIn;
1448 catch (Exception ex)
1450 var errorResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(response);
1451 string error = errorResponse?.ContainsKey(
"error") ==
true ? errorResponse[
"error"] :
"Unknown error";
1452 string errorDescription = errorResponse?.ContainsKey(
"error_description") ==
true ? errorResponse[
"error_description"] :
"No description";
1453 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to fetch AWS access token. Error: [{error}], Description: [{errorDescription}]",
null, GPALObjectType.None, ex);
1464 internal static class AzureHandler
1471 internal static string FetchAccessToken(Credentials creds)
1473 creds.AccessToken =
"";
1476 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Client ID or Client Secret is missing.",
null, GPALObjectType.None);
1480 string scopeUrl = Credentials.GetScopeValue(creds.
OAuthScope);
1485 var parameters = $
"grant_type=refresh_token&" +
1486 $
"client_id={Uri.EscapeDataString(creds.ClientId)}&" +
1487 $
"client_secret={Uri.EscapeDataString(creds.ClientSecret)}&" +
1488 $
"refresh_token={Uri.EscapeDataString(creds.RefreshToken)}&" +
1489 $
"scope={Uri.EscapeDataString(scopeUrl)}";
1491 var client = GPAL.RESTClient
1494 .WithParameters(parameters);
1495 var response = client.Execute();
1499 creds.AzureTokenResponse = System.Text.Json.JsonSerializer.Deserialize<AzureTokenResponse>(response);
1501 if (creds.AzureTokenResponse !=
null)
1503 creds.AccessToken = creds.AzureTokenResponse.AccessToken ??
"";
1507 creds.ExpiresIn = creds.AzureTokenResponse.
ExpiresIn;
1508 creds.IssueTime = DateTime.UtcNow;
1511 if (!
string.IsNullOrEmpty(creds.AzureTokenResponse.
RefreshToken))
1518 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to refresh Azure access token. Response: [{response}]",
null, GPALObjectType.None);
1521 catch (Exception ex)
1523 var errorResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(response);
1524 string error = errorResponse?.ContainsKey(
"error") ==
true ? errorResponse[
"error"] :
"Unknown error";
1525 string errorDescription = errorResponse?.ContainsKey(
"error_description") ==
true ? errorResponse[
"error_description"] :
"No description";
1526 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to refresh Azure access token. Error: [{error}], Description: [{errorDescription}]",
null, GPALObjectType.None, ex);
1531 creds.AuthCode = AuthHandler.GetAuthCode(creds);
1533 if (
string.IsNullOrEmpty(creds.
AuthCode))
1535 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"No authorization code provided.",
null, GPALObjectType.None);
1540 var parameters = $
"grant_type=authorization_code&" +
1541 $
"client_id={Uri.EscapeDataString(creds.ClientId)}&" +
1542 $
"client_secret={Uri.EscapeDataString(creds.ClientSecret)}&" +
1543 $
"code={Uri.EscapeDataString(creds.AuthCode)}&" +
1544 $
"redirect_uri={Uri.EscapeDataString(creds.Config.AzureRedirectUri)}&" +
1545 $
"scope={Uri.EscapeDataString(scopeUrl)}";
1547 var client = GPAL.RESTClient
1550 .WithParameters(parameters);
1551 var response = client.Execute();
1555 creds.AzureTokenResponse = System.Text.Json.JsonSerializer.Deserialize<AzureTokenResponse>(response);
1557 if (creds.AzureTokenResponse !=
null)
1559 creds.AccessToken = creds.AzureTokenResponse.AccessToken ??
"";
1563 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to fetch Azure access token. Response: [{response}]",
null, GPALObjectType.None);
1565 else if (!
string.IsNullOrEmpty(creds.AzureTokenResponse?.
RefreshToken))
1572 creds.ExpiresIn = creds.AzureTokenResponse.
ExpiresIn;
1573 creds.IssueTime = DateTime.UtcNow;
1577 catch (Exception ex)
1579 var errorResponse = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(response);
1580 string error = errorResponse?.ContainsKey(
"error") ==
true ? errorResponse[
"error"] :
"Unknown error";
1581 string errorDescription = errorResponse?.ContainsKey(
"error_description") ==
true ? errorResponse[
"error_description"] :
"No description";
1582 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Failed to fetch Azure access token. Error: [{error}], Description: [{errorDescription}]",
null, GPALObjectType.None, ex);
1594 internal static class GoogleHandler
1601 internal static string FetchAccessToken(Credentials creds)
1603 creds.AccessToken =
"";
1613 var parts = Credentials.SafeSplit(creds.
ServiceKey);
1614 clientId = parts.Item1;
1615 clientSecret = parts.Item2;
1618 if (
string.IsNullOrEmpty(clientId) ||
string.IsNullOrEmpty(clientSecret))
1619 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Google needs a client id and a client secret. Give them to WithClientId and WithClientSecret, or both to WithServiceKey as [id:secret].");
1623 var parameters = $
"grant_type=refresh_token&" +
1624 $
"client_id={Uri.EscapeDataString(clientId)}&" +
1625 $
"client_secret={Uri.EscapeDataString(clientSecret)}&" +
1626 $
"refresh_token={Uri.EscapeDataString(creds.RefreshToken)}";
1628 var response = GPAL.RESTClient
1631 .WithParameters(parameters)
1632 .WithEncoding(ContentEncoding.UrlEncoded)
1635 creds.GoogleTokenResponse = System.Text.Json.JsonSerializer.Deserialize<GoogleTokenResponse>(response);
1636 creds.AccessToken = creds.GoogleTokenResponse?.
AccessToken ??
"";
1640 creds.IssueTime = DateTime.UtcNow;
1641 creds.ExpiresIn = creds.GoogleTokenResponse.
ExpiresIn;
1645 GPAL.PublishSimpleEvent(
1646 GPALEventType.ERROR,
1647 $
"Failed to refresh Google access token. Response: [{response}]",
1649 GPALObjectType.None);
1654 creds.AuthCode = AuthHandler.GetAuthCode(creds);
1656 if (
string.IsNullOrEmpty(creds.
AuthCode))
1658 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"No authorization code provided.",
null, GPALObjectType.None);
1662 string redirectUri = creds.Config.UseRestRedirect
1663 ? creds.Config.GoogleRestRedirectUri
1666 var parameters = $
"{creds.Config.GrantTypeKey}=authorization_code&" +
1667 $
"client_id={Uri.EscapeDataString(clientId)}&" +
1668 $
"client_secret={Uri.EscapeDataString(clientSecret)}&" +
1669 $
"{creds.Config.GoogleAuthCodeKey}={Uri.EscapeDataString(creds.AuthCode)}&" +
1670 $
"redirect_uri={Uri.EscapeDataString(redirectUri)}";
1672 var response = GPAL.RESTClient
1675 .WithParameters(parameters)
1676 .WithEncoding(ContentEncoding.UrlEncoded)
1679 creds.GoogleTokenResponse = System.Text.Json.JsonSerializer.Deserialize<GoogleTokenResponse>(response);
1680 creds.AccessToken = creds.GoogleTokenResponse?.
AccessToken ??
"";
1684 creds.IssueTime = DateTime.UtcNow;
1685 creds.ExpiresIn = creds.GoogleTokenResponse.
ExpiresIn;
1689 GPAL.PublishSimpleEvent(
1690 GPALEventType.ERROR,
1691 $
"Failed to fetch Google access token. Response: [{response}]",
1693 GPALObjectType.None);
1696 if (!
string.IsNullOrEmpty(creds.GoogleTokenResponse?.
RefreshToken))
1711 public static string Base64UrlEncode(
byte[] input)
1713 return Convert.ToBase64String(input)
1724 private static byte[] Base64UrlDecode(
string input)
1726 string padded = input
1730 switch (padded.Length % 4)
1732 case 2: padded +=
"==";
break;
1733 case 3: padded +=
"=";
break;
1736 return Convert.FromBase64String(padded);
1744 internal static string FetchServiceAccountToken(Credentials creds)
1746 creds.AccessToken =
"";
1748 if (
string.IsNullOrEmpty(creds.ServiceAccountKeyJson))
1750 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Service account key JSON is missing.",
null, GPALObjectType.None);
1756 var serviceAccount = System.Text.Json.JsonSerializer.Deserialize<GoogleServiceAccountKey>(creds.ServiceAccountKeyJson);
1758 var iat = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
1759 var exp = iat + 3600;
1761 var pemReader =
new PemReader(
new StringReader(serviceAccount.PrivateKey));
1762 var keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
1763 var privateKey = (Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters)keyPair.Private;
1765 var header =
new { alg =
"RS256", typ =
"JWT" };
1769 iss = serviceAccount.ClientEmail,
1770 scope = Credentials.GetScopeValue(creds.
OAuthScope),
1775 var headerJson = JsonSerializer.Serialize(header);
1776 var payloadJson = JsonSerializer.Serialize(payload);
1778 var headerBytes = Encoding.UTF8.GetBytes(headerJson);
1779 var payloadBytes = Encoding.UTF8.GetBytes(payloadJson);
1781 string headerBase64Url = Base64UrlEncode(headerBytes);
1782 string payloadBase64Url = Base64UrlEncode(payloadBytes);
1784 string unsignedJwt = $
"{headerBase64Url}.{payloadBase64Url}";
1786 var signer = SignerUtilities.GetSigner(
"SHA256withRSA");
1787 signer.Init(
true, privateKey);
1788 var bytes = Encoding.UTF8.GetBytes(unsignedJwt);
1789 signer.BlockUpdate(bytes, 0, bytes.Length);
1791 var signatureBytes = signer.GenerateSignature();
1792 var signature = Base64UrlEncode(signatureBytes);
1794 var jwt = $
"{unsignedJwt}.{signature}";
1796 var parameters = $
"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={Uri.EscapeDataString(jwt)}";
1798 var response = GPAL.RESTClient
1801 .WithParameters(parameters)
1802 .WithEncoding(ContentEncoding.UrlEncoded)
1803 .WithHttpMethod(
"POST")
1806 var authData = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(response);
1812 creds.IssueTime = DateTime.UtcNow;
1814 if (authData !=
null &&
1815 authData.TryGetValue(
"expires_in", out var expStr) &&
1816 int.TryParse(expStr, out
int expires))
1818 creds.ExpiresIn = expires;
1822 creds.ExpiresIn = 0;
1827 GPAL.PublishSimpleEvent(
1828 GPALEventType.ERROR,
1829 $
"Failed to fetch service account token. Response: [{response}]",
1831 GPALObjectType.None);
1834 catch (Exception ex)
1836 GPAL.PublishSimpleEvent(
1837 GPALEventType.ERROR,
1838 $
"Failed to fetch service account token",
1840 GPALObjectType.None, ex);
1850 internal static class BitwardenHandler
1857 internal static List<CredentialResult> Handle(Credentials creds)
1859 var results =
new List<CredentialResult>();
1861 creds.AccessToken = FetchAccessToken(creds);
1866 var vaultJson = GPAL.RESTClient
1869 .WithParameters(
new Dictionary<string, string>
1876 var vaultData = JsonSerializer.Deserialize<BitwardenVaultResponse>(vaultJson);
1878 if (vaultData?.Data !=
null)
1880 foreach (var item
in vaultData.Data)
1882 if (item.Login !=
null &&
1883 Credentials.MatchesDomain(item.Login.Uris?.Select(u => u.Uri), creds.
Domain))
1884 results.Add(
new CredentialResult(item.Login.Username, item.Login.Password));
1896 internal static string FetchAccessToken(Credentials creds)
1898 var (clientId, clientSecret) = Credentials.SafeSplit(creds.
ServiceKey);
1900 var response = GPAL.RESTClient
1905 grant_type =
"password",
1907 password = creds.Password,
1908 client_id = clientId,
1909 client_secret = clientSecret
1913 creds.BitwardenTokenResponse = JsonSerializer.Deserialize<BitwardenTokenResponse>(response);
1914 creds.AccessToken = creds.BitwardenTokenResponse?.
AccessToken ??
"";
1918 creds.IssueTime = DateTime.UtcNow;
1919 creds.ExpiresIn = creds.BitwardenTokenResponse.
ExpiresIn;
1923 GPAL.PublishSimpleEvent(
1924 GPALEventType.ERROR,
1925 $
"Bitwarden auth failed. Response: [{response}]",
1927 GPALObjectType.None);
1937 internal static class LastPassHandler
1944 internal static List<CredentialResult> Handle(Credentials creds)
1946 var results =
new List<CredentialResult>();
1949 if (!EnsureLoggedIn(creds))
1955 var process =
new Process
1957 StartInfo =
new ProcessStartInfo
1960 Arguments =
"ls --json",
1961 RedirectStandardOutput =
true,
1962 UseShellExecute =
false,
1963 CreateNoWindow =
true
1968 string jsonOutput = process.StandardOutput.ReadToEnd();
1969 process.WaitForExit();
1971 if (process.ExitCode != 0)
1973 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"LastPass CLI returned exit code [{process.ExitCode}].",
null, GPALObjectType.None);
1977 var items = JsonSerializer.Deserialize<List<LastPassItem>>(jsonOutput);
1981 string searchTerm = (creds.Target ??
"").ToLowerInvariant();
1983 foreach (var item
in items)
1985 if (
string.IsNullOrEmpty(item.Username) &&
string.IsNullOrEmpty(item.Password))
1988 bool matches =
string.IsNullOrEmpty(searchTerm) ||
1989 (item.Name?.ToLowerInvariant().Contains(searchTerm) ==
true) ||
1990 (item.Url?.ToLowerInvariant().Contains(searchTerm) ==
true) ||
1991 (item.Username?.ToLowerInvariant().Contains(searchTerm) ==
true);
1993 if (matches && Credentials.MatchesDomain(item.Url, creds.
Domain))
1995 results.Add(
new CredentialResult(item.Username ??
"", item.Password ??
""));
2000 catch (Exception ex)
2002 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"LastPass vault retrieval failed",
null, GPALObjectType.None, ex);
2013 internal static string FetchAccessToken(Credentials creds)
2017 EnsureLoggedIn(creds);
2018 return "LastPass-CLI-Session";
2026 private static bool EnsureLoggedIn(Credentials creds)
2030 var statusProcess =
new Process
2032 StartInfo =
new ProcessStartInfo
2035 Arguments =
"status",
2036 RedirectStandardOutput =
true,
2037 UseShellExecute =
false,
2038 CreateNoWindow =
true
2042 statusProcess.Start();
2043 string statusOutput = statusProcess.StandardOutput.ReadToEnd().Trim();
2044 statusProcess.WaitForExit();
2046 if (statusOutput.ToLower().Contains(
"logged in"))
2050 GPAL.PublishSimpleEvent(
2051 GPALEventType.ERROR,
2052 $
"LastPass CLI is not logged in. Please run this command once on the machine:\n" +
2053 $
"lpass login [{creds.Username}]\n" +
2054 $
"Then re-run your automation.",
2055 null, GPALObjectType.None);
2059 catch (Exception ex)
2061 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2062 $
"LastPass CLI check failed. Is the LastPass CLI (lpass) installed?",
2063 null, GPALObjectType.None, ex);
2072 internal static class OnePasswordHandler
2079 internal static List<CredentialResult> Handle(Credentials creds)
2081 var results =
new List<CredentialResult>();
2084 if (!EnsureSession(creds))
2089 string search = creds.
Target?.Trim() ??
"";
2092 string arguments =
string.IsNullOrEmpty(search)
2093 ?
"item list --format=json"
2094 : $
"item list --search=\"{search}\" --format=json";
2096 var process =
new Process
2098 StartInfo =
new ProcessStartInfo
2101 Arguments = arguments,
2102 RedirectStandardOutput =
true,
2103 UseShellExecute =
false,
2104 CreateNoWindow =
true
2109 string jsonOutput = process.StandardOutput.ReadToEnd();
2110 process.WaitForExit();
2112 if (process.ExitCode != 0)
2114 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2115 $
"1Password CLI returned exit code [{process.ExitCode}]. Make sure you are signed in.",
2116 null, GPALObjectType.None);
2120 var items = JsonSerializer.Deserialize<List<OnePasswordItem>>(jsonOutput);
2124 foreach (var item
in items)
2127 string username =
"";
2128 string password =
"";
2130 foreach (var field
in item.Fields)
2132 if (
string.Equals(field.Label,
"username", StringComparison.OrdinalIgnoreCase) ||
2133 string.Equals(field.Type,
"username", StringComparison.OrdinalIgnoreCase))
2135 username = field.Value ??
"";
2137 else if (
string.Equals(field.Label,
"password", StringComparison.OrdinalIgnoreCase) ||
2138 string.Equals(field.Type,
"password", StringComparison.OrdinalIgnoreCase))
2140 password = field.Value ?? field.Reference ??
"";
2144 if ((!
string.IsNullOrEmpty(username) || !
string.IsNullOrEmpty(password)) &&
2145 Credentials.MatchesDomain(item.Urls?.Select(u => u.Href), creds.
Domain))
2147 results.Add(
new CredentialResult(username, password));
2152 catch (Exception ex)
2154 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2155 $
"1Password vault retrieval failed",
null, GPALObjectType.None, ex);
2166 internal static string FetchAccessToken(Credentials creds)
2169 EnsureSession(creds);
2170 return "1Password-CLI-Session";
2178 private static bool EnsureSession(Credentials creds)
2183 var process =
new Process
2185 StartInfo =
new ProcessStartInfo
2188 Arguments =
"whoami --format=json",
2189 RedirectStandardOutput =
true,
2190 UseShellExecute =
false,
2191 CreateNoWindow =
true
2196 string output = process.StandardOutput.ReadToEnd();
2197 process.WaitForExit();
2199 if (process.ExitCode == 0 && !
string.IsNullOrWhiteSpace(output))
2203 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2204 $
"1Password CLI is not signed in. Please run this once on the target machine:\n" +
2206 $
"Then re-run your automation. (Session lasts ~30 minutes of activity)",
2207 null, GPALObjectType.None);
2211 catch (Exception ex)
2213 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2214 $
"1Password CLI check failed. Is the 1Password CLI (`op`) installed and in PATH?",
2215 null, GPALObjectType.None, ex);
2224 internal static class DashlaneHandler
2231 internal static List<CredentialResult> Handle(Credentials creds)
2233 var results =
new List<CredentialResult>();
2236 if (!EnsureLoggedIn(creds))
2241 string search = creds.
Target?.Trim() ??
"";
2244 string arguments =
string.IsNullOrEmpty(search)
2245 ?
"password list --json"
2246 : $
"password list --search \"{search}\" --json";
2248 var process =
new Process
2250 StartInfo =
new ProcessStartInfo
2253 Arguments = arguments,
2254 RedirectStandardOutput =
true,
2255 RedirectStandardError =
true,
2256 UseShellExecute =
false,
2257 CreateNoWindow =
true
2262 string jsonOutput = process.StandardOutput.ReadToEnd();
2263 string errorOutput = process.StandardError.ReadToEnd();
2264 process.WaitForExit();
2266 if (process.ExitCode != 0)
2268 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2269 $
"Dashlane CLI failed (exit code [{process.ExitCode}]). Error: [{errorOutput.Trim()}]",
2270 null, GPALObjectType.None);
2274 var items = JsonSerializer.Deserialize<List<DashlaneItem>>(jsonOutput);
2278 foreach (var item
in items)
2280 if (!
string.IsNullOrEmpty(item.Username) || !
string.IsNullOrEmpty(item.Password))
2282 if (Credentials.MatchesDomain(item.Url, creds.
Domain))
2283 results.Add(
new CredentialResult(item.Username ??
"", item.Password ??
""));
2288 catch (Exception ex)
2290 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2291 $
"Dashlane vault retrieval failed",
null, GPALObjectType.None, ex);
2302 internal static string FetchAccessToken(Credentials creds)
2305 EnsureLoggedIn(creds);
2306 return "Dashlane-CLI-Session";
2314 private static bool EnsureLoggedIn(Credentials creds)
2318 var process =
new Process
2320 StartInfo =
new ProcessStartInfo
2323 Arguments =
"status --json",
2324 RedirectStandardOutput =
true,
2325 UseShellExecute =
false,
2326 CreateNoWindow =
true
2331 string output = process.StandardOutput.ReadToEnd();
2332 process.WaitForExit();
2334 if (process.ExitCode == 0 && output.ToLower().Contains(
"\"logged_in\":true"))
2338 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2339 $
"Dashlane CLI is not logged in. Please run this once on the target machine:\n" +
2341 $
"Then re-run your automation.",
2342 null, GPALObjectType.None);
2346 catch (Exception ex)
2348 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2349 $
"Dashlane CLI check failed. Is the Dashlane CLI (`dcli`) installed and in PATH?",
2350 null, GPALObjectType.None, ex);
2359 internal static class KeeperHandler
2366 internal static List<CredentialResult> Handle(Credentials creds)
2368 var results =
new List<CredentialResult>();
2370 if (!EnsureLoggedIn(creds))
2375 string search = creds.
Target?.Trim() ??
"";
2378 string arguments =
string.IsNullOrEmpty(search)
2379 ?
"list --format=json"
2380 : $
"search \"{search}\" --format=json";
2382 var process =
new Process
2384 StartInfo =
new ProcessStartInfo
2386 FileName =
"keeper",
2387 Arguments = arguments,
2388 RedirectStandardOutput =
true,
2389 RedirectStandardError =
true,
2390 UseShellExecute =
false,
2391 CreateNoWindow =
true
2396 string jsonOutput = process.StandardOutput.ReadToEnd();
2397 string errorOutput = process.StandardError.ReadToEnd();
2398 process.WaitForExit();
2400 if (process.ExitCode != 0)
2402 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2403 $
"Keeper Commander returned exit code [{process.ExitCode}]. Error: [{errorOutput.Trim()}]",
2404 null, GPALObjectType.None);
2408 var records = JsonSerializer.Deserialize<List<KeeperRecord>>(jsonOutput);
2410 if (records !=
null)
2412 foreach (var record
in records)
2414 string username = record.Login?.Username ??
"";
2415 string password = record.Login?.Password ??
"";
2417 if ((!
string.IsNullOrEmpty(username) || !
string.IsNullOrEmpty(password)) &&
2418 Credentials.MatchesDomain(record.Login?.Url, creds.
Domain))
2420 results.Add(
new CredentialResult(username, password));
2425 catch (Exception ex)
2427 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2428 $
"Keeper vault retrieval failed",
null, GPALObjectType.None, ex);
2439 internal static string FetchAccessToken(Credentials creds)
2442 EnsureLoggedIn(creds);
2443 return "Keeper-Commander-Session";
2451 private static bool EnsureLoggedIn(Credentials creds)
2455 var process =
new Process
2457 StartInfo =
new ProcessStartInfo
2459 FileName =
"keeper",
2460 Arguments =
"whoami --format=json",
2461 RedirectStandardOutput =
true,
2462 UseShellExecute =
false,
2463 CreateNoWindow =
true
2468 string output = process.StandardOutput.ReadToEnd();
2469 process.WaitForExit();
2471 if (process.ExitCode == 0 && !
string.IsNullOrWhiteSpace(output))
2474 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2475 $
"Keeper Commander is not logged in. Please run this once on the target machine:\n" +
2477 $
"Then login with your email / master password (or use 'keeper login').\n" +
2478 $
"After login, you can use 'this-device' for persistent sessions.",
2479 null, GPALObjectType.None);
2483 catch (Exception ex)
2485 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
2486 $
"Keeper Commander check failed. Is Keeper Commander (`keeper`) installed and in PATH?",
2487 null, GPALObjectType.None, ex);
2498 internal static class LocalAuthCallbackServer
2500 private static readonly
object _lock =
new object();
2501 private static HttpListener _listener;
2502 private static string _pendingAuthCode;
2503 private static string _pendingAuthScope;
2510 internal static bool StartIfNeeded(
string baseUrl)
2514 if (
null != _listener)
2519 var listener =
new HttpListener();
2520 listener.Prefixes.Add(baseUrl);
2522 _listener = listener;
2524 new Thread(() => Listen(listener)) { IsBackground =
true }.Start();
2527 catch (HttpListenerException)
2536 private static void Listen(HttpListener listener)
2540 while (listener.IsListening)
2542 HttpListenerContext context = listener.GetContext();
2543 HandleRequest(context);
2552 private static void HandleRequest(HttpListenerContext context)
2554 HttpListenerResponse response = context.Response;
2555 string path = context.Request.Url.AbsolutePath.Trim(
'/');
2560 case "access-token":
2561 NameValueCollection query = ParseQueryString(context.Request.Url.Query);
2565 _pendingAuthCode = query[
"code"];
2566 _pendingAuthScope = query[
"scope"];
2569 SendResponse(response,
"{\"message\":\"Authorization code received. You can now call /get-access-token.\"}");
2572 case "get-access-token":
2577 code = _pendingAuthCode ??
string.Empty;
2578 scope = _pendingAuthScope ??
string.Empty;
2581 _pendingAuthCode =
null;
2582 _pendingAuthScope =
null;
2585 json = System.Text.Json.JsonSerializer.Serialize(
new { code, scope });
2586 SendResponse(response, json);
2590 SendResponse(response,
"{}");
2595 private static NameValueCollection ParseQueryString(
string query)
2597 var result =
new NameValueCollection();
2599 if (
string.IsNullOrEmpty(query))
2602 foreach (
string pair
in query.TrimStart(
'?').Split(
'&'))
2604 if (
string.IsNullOrEmpty(pair))
2607 string[] parts = pair.Split(
new[] {
'=' }, 2);
2608 string key = Uri.UnescapeDataString(parts[0]);
2609 string value = parts.Length > 1 ? Uri.UnescapeDataString(parts[1]) :
string.Empty;
2610 result[key] = value;
2616 private static void SendResponse(HttpListenerResponse response,
string message)
2620 response.StatusCode = (int)HttpStatusCode.OK;
2621 response.ContentType =
"application/json";
2622 response.ContentEncoding = Encoding.UTF8;
2624 byte[] buffer = Encoding.UTF8.GetBytes(message ??
"");
2625 response.OutputStream.Write(buffer, 0, buffer.Length);
2630 try { response.Close(); }
catch { }
2637 internal static void Stop()
2641 if (
null == _listener)
2644 try { _listener.Stop(); _listener.Close(); }
catch { }
2646 _pendingAuthCode =
null;
2647 _pendingAuthScope =
null;
2655 internal static class AuthHandler
2658 internal const string DefaultTokenUrl =
"http://localhost:3000/";
2666 internal static string GetAuthCodeFromRest(Process browserProcess,
int timeoutInSeconds)
2668 string authCode =
"";
2675 var restClient = GPAL.RESTClient
2676 .WithAPIBase(GPAL.OAuthTokenUrl)
2677 .WithEndpoint(
"get-access-token");
2680 TimeSpan timeout = TimeSpan.FromSeconds(timeoutInSeconds);
2681 TimeSpan interval = TimeSpan.FromSeconds(1);
2682 DateTime start = DateTime.UtcNow;
2684 while ((DateTime.UtcNow - start) < timeout)
2686 AuthCodePayload tokenInfo = restClient.Execute<AuthCodePayload>();
2687 if (!
string.IsNullOrEmpty(tokenInfo?.Code))
2689 authCode = tokenInfo.
Code;
2690 GPAL.PublishSimpleEvent(
2692 "Authorization code retrieved from REST server.",
2699 Thread.Sleep(interval);
2702 if (
string.IsNullOrEmpty(authCode))
2703 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Timeout waiting for authorization code from REST server.",
null, GPALObjectType.None);
2705 catch (Exception ex)
2707 GPAL.PublishSimpleEvent(
2708 GPALEventType.ERROR,
2709 $
"REST redirect auth failed",
2714 if (
null != browserProcess)
2715 BrowserHelper.KillProcess(browserProcess);
2741 internal static bool ResolveTokenUrl(Credentials creds)
2743 bool retVal =
false;
2746 GPAL.OAuthTokenUrl =
true ==
string.IsNullOrWhiteSpace(configured)
2748 : $
"{new Uri(configured).GetLeftPart(UriPartial.Authority)}/";
2750 if (
true == LocalAuthCallbackServer.StartIfNeeded(GPAL.OAuthTokenUrl))
2754 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Listening on [{GPAL.OAuthTokenUrl}] to catch the token.", creds, GPALObjectType.Other);
2756 else if (
true == Browser.BrowserHelper.RestApiAnswering(GPAL.OAuthTokenUrl))
2758 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"The GPALRestAPI already on [{GPAL.OAuthTokenUrl}] will catch the token.", creds, GPALObjectType.Other);
2764 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);
2772 [System.Runtime.InteropServices.DllImport(
"kernel32.dll")]
2773 private static extern IntPtr GetConsoleWindow();
2775 internal static string GetAuthCode(Credentials creds)
2777 string authCode =
"";
2779 Process browserProcess =
null;
2780 bool ownsLocalServer =
false;
2782 bool publishToConsole = GPAL.GPALSettings.PublishToConsole;
2784 GPALEventType consoleEvents = GPAL.GPALSettings.ConsoleEvents;
2786 if (
false == publishToConsole)
2787 GPAL.WithPublishToConsole();
2788 else if (
false == consoleEvents.HasFlag(GPALEventType.INFO))
2789 GPAL.ConsoleEvents |= GPALEventType.INFO;
2799 ownsLocalServer = ResolveTokenUrl(creds);
2801 string authUrl = AuthHandler.GetAuthUrl(creds);
2803 browserProcess = creds.Browser.
MagicHelper.LaunchBrowser(creds.Browser, authUrl);
2804 authCode = GetAuthCodeFromRest(browserProcess, 60);
2808 if (
string.IsNullOrEmpty(authCode))
2810 string authUrl = AuthHandler.GetAuthUrl(creds);
2813 GPAL.PublishSimpleEvent(
2814 GPALEventType.WARNING,
2815 "No authorization code arrived via the local redirect listener. Falling back to manual entry.",
2824 if (IntPtr.Zero == GetConsoleWindow())
2826 GPAL.PublishSimpleEvent(
2827 GPALEventType.ERROR,
2828 $
"An authorization code is needed and this program has no console to type it into. Open this URL, authorize, and hand the code back with WithAuthCode, or supply a refresh token with WithRefreshToken: [{authUrl}]");
2834 GPAL.PublishSimpleEvent(
2836 $
"Open this URL in a browser, authorize, and provide the code: \n[{authUrl}]",
2841 Console.WriteLine(
"\nEnter auth code: ");
2842 authCode = Console.ReadLine();
2844 if (
string.IsNullOrEmpty(authCode))
2846 GPAL.PublishSimpleEvent(
2847 GPALEventType.ERROR,
2848 "No authorization code provided.",
2862 if (ownsLocalServer)
2863 LocalAuthCallbackServer.Stop();
2865 if (
false == publishToConsole)
2866 GPAL.WithPublishToConsole(GPALEventType.NONE);
2867 else if (
false == consoleEvents.HasFlag(GPALEventType.INFO))
2868 GPAL.ConsoleEvents ^= GPALEventType.INFO;
2878 internal static string GetAuthUrl(Credentials creds)
2880 switch (creds.ServiceType)
2882 case CredentialServiceType.Google:
2883 return $
"{creds.Config.GoogleConsentBase}/o/oauth2/v2/auth" +
2884 $
"?client_id={Uri.EscapeDataString(creds.ClientId)}" +
2885 $
"&redirect_uri={Uri.EscapeDataString(creds.Config.UseRestRedirect ? creds.Config.GoogleRestRedirectUri : creds.Config.GoogleRedirectUri)}" +
2886 $
"&response_type=code" +
2887 $
"&scope={Uri.EscapeDataString(Credentials.GetScopeValue(creds.OAuthScope))}" +
2888 $
"&access_type=offline";
2890 case CredentialServiceType.Azure:
2891 return $
"{creds.Config.AzureAuthBase}/oauth2/v2.0/authorize" +
2892 $
"?client_id={Uri.EscapeDataString(creds.ClientId)}" +
2893 $
"&response_type=code" +
2894 $
"&redirect_uri={Uri.EscapeDataString(creds.Config.AzureRedirectUri)}" +
2895 $
"&response_mode=query" +
2896 $
"&scope={Uri.EscapeDataString(Credentials.GetScopeValue(creds.OAuthScope))}";
2898 case CredentialServiceType.AWS:
2899 return $
"{creds.Config.AWSAuthBase}/oauth2/authorize" +
2900 $
"?client_id={Uri.EscapeDataString(creds.ClientId)}" +
2901 $
"&response_type=code" +
2902 $
"&redirect_uri={Uri.EscapeDataString(creds.Config.AWSRedirectUri)}" +
2903 $
"&scope={Uri.EscapeDataString(Credentials.GetScopeValue(creds.OAuthScope))}";
2905 case CredentialServiceType.Bitwarden:
2906 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.
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...
IAllowOAuthClient WithWebAuth(WebAuthType webAuthType)
How the site expects to be told who you are. A browser handed this credential acts on it accordingly,...
IAllowOAuthClient WithPassword(string password)
Sets the password, either as a credential to retrieve directly (with CredentialServiceType....
CredentialsConfig Config
Endpoint/protocol configuration for the credential service handlers, loaded from ....
IAllowCredentialSettings WithServiceKey(string key)
Service key is a custom string consisting of your ClientID:ClientSecret.
OAuthScope OAuthScope
OAuth scope requested via .WithScope when fetching an access token.
IAllowCredentialSettings WithDomain(string domain)
Restricts GetCredentialsFor/SaveTo results to vault items whose URL contains this domain....
string Username
Username supplied via .WithUsername, or the username field returned from a vault item.
IAllowKeyFromApiEndpoint WithKeyFromApi(IRESTClient restClient)
Like WithServiceKey, but fetches the key via a GET request made with the supplied,...
IAllowOAuthClient WithAuthRedirectUrl(string redirectUrl)
Where the OAuth redirect lands, and therefore what has to catch the token. This is the one setting th...
string Domain
Optional URL filter supplied via .WithDomain, used to narrow vault matches down to a specific site.
IAllowCredentialSettings WithMasterCredentials(ICredentials masterCredentials)
Supplies the login for the credential service itself, your Bitwarden, LastPass or 1Password account,...
string ClientId
OAuth client ID supplied via .WithClientId or derived from .WithServiceKey.
IAllowClientSecret WithClientId(string clientId)
Sets the OAuth client ID used to authenticate to ServiceType.
IAllowCredentialSettings WithServiceAccountKey(GoogleServiceAccountKey serviceAccountKey)
Supplies a Google Cloud service account key as a deserialized object, used to fetch an access token w...
string AuthCode
OAuth authorization code supplied via .WithAuthCode, or retrieved automatically during the OAuth redi...
IAllowCredentialSettings WithAuthCode(string authCode)
Supplies a previously-obtained OAuth authorization code, skipping the interactive authorization step.
IAllowCredentialMethod WithService(CredentialServiceType serviceType)
Selects which password manager or cloud identity service this credentials request targets.
IAllowCredentialSettings WithScope(OAuthScope scope)
Sets the OAuth scope to request when an access token is fetched.
IAllowCredentialSettings WithEndpoint(string endpoint)
Completes .WithKeyFromApi(restClient) by calling the given endpoint on that RESTClient and using the ...
IAllowCredentialSettings WithKeyFromEnv(string envVariableName)
Like WithServiceKey, but reads the key from an environment variable (checking process,...
IAllowCredentialSettings GetCredentialsFor(string target)
Sets the search term used to find a matching item in the target password manager (e....
string AccessToken
Most recently fetched access token for ServiceType, set by FetchAccessToken/GetCredentialsFor.
string ClientSecret
OAuth client secret supplied via .WithClientSecret or derived from .WithServiceKey.
IAllowCredentialSettings WithServiceAccountKey(GPALFile serviceAccountKeyFile)
Loads a Google Cloud service account key from a JSON key file, used to fetch an access token without ...
IAllowCredentialSettings WithServiceAccountKey(string serviceAccountKey)
Supplies a Google Cloud service account key as a raw JSON string, used to fetch an access token witho...
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 ...
IAllowOAuthClient WithAccessToken(string accessToken)
A token the workflow already holds, for the schemes that present one: Bearer, X-Auth-Token,...
void FetchAccessToken(out string accessToken)
Fetches a new access token for ServiceType using the credentials/configuration supplied so far,...
IAllowCredentialSettings WithRefreshToken(string refreshToken)
Supplies a previously-obtained OAuth refresh token, used to fetch a new access token without re-autho...
IAllowCredentialSettings WithClientSecret(string clientSecret)
Sets the OAuth client secret and derives ServiceKey as "ClientId:ClientSecret".
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...
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.
The ways a credential can identify itself, which is everything WithService leads to.