Welcome to the Inedo Forums! Check out the Forums Guide for help getting started.
If you are experiencing any issues with the forum software, please visit the Contact Form on our website and let us know!
Working with Secure Resources / Secure Credentials
-
I recently had a need to work with both a Secure Resource and Secure Credential from within an Otter script, but found it was difficult to determine what property names were available when using
$SecureCredentialProperty(...)and$SecureResourceProperty(...), as they were not fully-documented and are dynamically generated based on the resource/credential type (which may themselves be provided by plugins).To compensate, I knocked up a couple of variable functions in an scratch extension of my own. I figured they might be useful for others, and so am providing them below.
Long term, they are possibly best-served living in OtterEx.dll, alongside the existing
$SecureCredentialPropertyfunction, but I don't think that it available via your GitHub for pull-requests (I had to run OtterEx.dll through a decompiler to figure out what was needed).In any case, if they are of any use to you, you are more than welcome to them...
SecureResourcePropertiesVariableFunction.cs
using System.Collections; using System.ComponentModel; using System.Reflection; using Inedo.ExecutionEngine.Executer; using Inedo.Extensibility; using Inedo.Extensibility.Credentials; using Inedo.Extensibility.VariableFunctions; using Inedo.Serialization; using SecureCreds = Inedo.Extensibility.Credentials.SecureCredentials; namespace Inedo.Extensions.VariableFunctions.SecureCredentials { [ScriptAlias("SecureCredentialProperties")] [Description("Gets the properties available to a named Secure Credential. Use `$SecureCredentialProperty()` to obtain the value.")] public sealed class SecureCredentialPropertiesVariableFunction : VectorVariableFunction { [VariableFunctionParameter(0)] [ScriptAlias("credential")] [Description("The name of the credential for which to fetch property names")] public string? CredentialName { get; set; } protected override IEnumerable? EvaluateVector(IVariableFunctionContext context) => GetProperties(CredentialName, context).Select(p => p.Name); internal static IEnumerable<PropertyInfo> GetProperties(string? credentialName, IVariableFunctionContext context) { var credentialResolutionContext = new CredentialResolutionContext(context.ProjectId, context.EnvironmentId); var secureCredential = SecureCreds.TryCreate(credentialName, credentialResolutionContext) ?? throw new ExecutionFailureException(string.Concat( "Could not find a Secure Credential named \"", credentialName, "\"; this error may occur if you renamed a credential, " + "or the application or environment in context does not " + "match any existing credentials. To resolve, edit this " + "item, property, or operation's configuration, ensure a " + "valid credential for the application/environment in " + "context is selected, and then save.")); return Persistence.GetPersistentProperties(secureCredential.GetType(), true); } } [ScriptAlias("SecureCredentialHasProperty")] [Description("Checks if the named property can be read from the named Secure Credential")] public sealed class SecureCredentialHasPropertyVariableFunction : ScalarVariableFunction { [VariableFunctionParameter(0)] [ScriptAlias("credential")] [Description("The name of the Secure Credential to test.")] public string? CredentialName { get; set; } [Description("The name of the property to test.")] [ScriptAlias("property")] [VariableFunctionParameter(1)] public string? PropertyName { get; set; } [Description("Set to `false` to return `false` if the property is found but encrypted " + "and/or requires additional script access permissions. Set to `true` " + "(i.e. the default) to test only whether the property exists.")] [ScriptAlias("whenEncrypted")] [VariableFunctionParameter(2, Optional=true)] public bool WhenEncrypted { get; set; } = true; protected override object? EvaluateScalar(IVariableFunctionContext context) { if (string.IsNullOrWhiteSpace(PropertyName)) return false; var prop = SecureCredentialPropertiesVariableFunction .GetProperties(CredentialName, context) .FirstOrDefault(p => string.Equals(p.Name, PropertyName, StringComparison.InvariantCultureIgnoreCase)); if (prop != null) { if (WhenEncrypted == false) { var encrypted = prop.GetCustomAttribute<PersistentAttribute>()?.Encrypted ?? false; if (encrypted) return false; } return true; } return false; } } }SecureCredentialPropertiesVariableFunction.cs
using System.Collections; using System.ComponentModel; using System.Reflection; using Inedo.ExecutionEngine.Executer; using Inedo.Extensibility; using Inedo.Extensibility.Credentials; using Inedo.Extensibility.VariableFunctions; using Inedo.Serialization; using SecureCreds = Inedo.Extensibility.Credentials.SecureCredentials; namespace Inedo.Extensions.VariableFunctions.SecureCredentials { [ScriptAlias("SecureCredentialProperties")] [Description("Gets the properties available to a named Secure Credential. Use `$SecureCredentialProperty()` to obtain the value.")] public sealed class SecureCredentialPropertiesVariableFunction : VectorVariableFunction { [VariableFunctionParameter(0)] [ScriptAlias("credential")] [Description("The name of the credential for which to fetch property names")] public string? CredentialName { get; set; } protected override IEnumerable? EvaluateVector(IVariableFunctionContext context) => GetProperties(CredentialName, context).Select(p => p.Name); internal static IEnumerable<PropertyInfo> GetProperties(string? credentialName, IVariableFunctionContext context) { var credentialResolutionContext = new CredentialResolutionContext(context.ProjectId, context.EnvironmentId); var secureCredential = SecureCreds.TryCreate(credentialName, credentialResolutionContext) ?? throw new ExecutionFailureException(string.Concat( "Could not find a Secure Credential named \"", credentialName, "\"; this error may occur if you renamed a credential, " + "or the application or environment in context does not " + "match any existing credentials. To resolve, edit this " + "item, property, or operation's configuration, ensure a " + "valid credential for the application/environment in " + "context is selected, and then save.")); return Persistence.GetPersistentProperties(secureCredential.GetType(), true); } } [ScriptAlias("SecureCredentialHasProperty")] [Description("Checks if the named property can be read from the named Secure Credential")] public sealed class SecureCredentialHasPropertyVariableFunction : ScalarVariableFunction { [VariableFunctionParameter(0)] [ScriptAlias("credential")] [Description("The name of the Secure Credential to test.")] public string? CredentialName { get; set; } [Description("The name of the property to test.")] [ScriptAlias("property")] [VariableFunctionParameter(1)] public string? PropertyName { get; set; } [Description("Set to `false` to return `false` if the property is found but encrypted " + "and/or requires additional script access permissions. Set to `true` " + "(i.e. the default) to test only whether the property exists.")] [ScriptAlias("whenEncrypted")] [VariableFunctionParameter(2, Optional=true)] public bool WhenEncrypted { get; set; } = true; protected override object? EvaluateScalar(IVariableFunctionContext context) { if (string.IsNullOrWhiteSpace(PropertyName)) return false; var prop = SecureCredentialPropertiesVariableFunction .GetProperties(CredentialName, context) .FirstOrDefault(p => string.Equals(p.Name, PropertyName, StringComparison.InvariantCultureIgnoreCase)); if (prop != null) { if (WhenEncrypted == false) { var encrypted = prop.GetCustomAttribute<PersistentAttribute>()?.Encrypted ?? false; // TODO: this should probably actually check if script access has been granted, but // this is buried in Inedo.Otter.Data.Tables.Credentials_Extended, and I'm not sure // if that is safe to wrap in an Extension // For now, we just determine if it would be encrypted, not whether we can access if (encrypted) return false; } return true; } return false; } } }
-
Looks like I pasted the SecureCredential... file twice (and can't edit/update now). The SecureResource... version is:
SecureResourcePropertiesVariableFunction.cs
using System.Collections; using System.ComponentModel; using Inedo.ExecutionEngine.Executer; using Inedo.Extensibility; using Inedo.Extensibility.SecureResources; using Inedo.Extensibility.VariableFunctions; using Inedo.Serialization; namespace Inedo.Extensions.VariableFunctions.SecureResources { [ScriptAlias("SecureResourceProperties")] [Description("Gets the properties available to a named Secure Resource. Use `$SecureResourceProperty()` to obtain the value.")] public sealed class SecureResourcePropertiesVariableFunction : VectorVariableFunction { [VariableFunctionParameter(0)] [ScriptAlias("resource")] [Description("The name of the Secure Resource for which to fetch property names.")] public string? ResourceName { get; set; } [Description("The type of the Secure Resource for which to fetch property names.")] [ScriptAlias("type")] [VariableFunctionParameter(1, Optional=true)] public SecureResourceType? ResourceType { get; set; } protected override IEnumerable? EvaluateVector(IVariableFunctionContext context) => GetPropertyNames(ResourceName, ResourceType, context); internal static IEnumerable<string> GetPropertyNames(string? resourceName, SecureResourceType? resourceType, IVariableFunctionContext context) { var resourceResolutionContext = new ResourceResolutionContext(context.ProjectId); var secureResource = SecureResource.TryCreate(resourceType.GetValueOrDefault(), resourceName, resourceResolutionContext) ?? throw BadResourceName(resourceName ?? "<null>"); return Persistence.GetPersistentProperties(secureResource.GetType(), true) .Select(p => p.Name); } internal static Exception BadResourceName(string resourceName) { return new ExecutionFailureException(string.Concat( "Could not find a Secure Resource named \"", resourceName, "\"; this error may occur if you renamed a resource, " + "or the application in context does not match any " + "existing resources. To resolve, edit this item, " + "property, or operation's configuration, ensure a " + "valid credential for the application in context is " + "selected, and then save.")); } } [ScriptAlias("SecureResourceHasProperty")] [Description("Checks if the named property can be read from the named Secure Resource")] public sealed class SecureResourceHasPropertyVariableFunction : ScalarVariableFunction { [VariableFunctionParameter(0)] [ScriptAlias("resource")] [Description("The name of the Secure Resource to test.")] public string? ResourceName { get; set; } [Description("The name of the property to test.")] [ScriptAlias("property")] [VariableFunctionParameter(1)] public string? PropertyName { get; set; } [Description("The type of the resource property to test.")] [ScriptAlias("type")] [VariableFunctionParameter(2, Optional=true)] public SecureResourceType? ResourceType { get; set; } protected override object? EvaluateScalar(IVariableFunctionContext context) { if (string.IsNullOrWhiteSpace(PropertyName)) return false; return SecureResourcePropertiesVariableFunction .GetPropertyNames(ResourceName, ResourceType, context) .Contains(PropertyName, StringComparer.InvariantCultureIgnoreCase); } } [ScriptAlias("SecureResourceCredential")] [Description("Gets the name of the Secure Credential assigned to a Secure Resource")] public sealed class SecureResourceCredentialVariableFunction : ScalarVariableFunction { [VariableFunctionParameter(0)] [ScriptAlias("resource")] [Description("Literal text value")] public string? ResourceName { get; set; } [Description("The type of the resource property to get.")] [ScriptAlias("type")] [VariableFunctionParameter(1, Optional=true)] public SecureResourceType? ResourceType { get; set; } protected override object EvaluateScalar(IVariableFunctionContext context) { var resourceResolutionContext = new ResourceResolutionContext(context.ProjectId); var resourceType = ResourceType; var secureResource = SecureResource.TryCreate( resourceType.GetValueOrDefault(), ResourceName, resourceResolutionContext) ?? throw SecureResourcePropertiesVariableFunction.BadResourceName(ResourceName ?? "<null>"); return secureResource?.CredentialName ?? string.Empty; } } }