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 $SecureCredentialProperty function, 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;
            }
        }
    }
    


Inedo Website HomeSupport HomeCode of ConductForums GuideDocumentation