diff --git a/src/D2L.Bmx/AwsCredsCreator.cs b/src/D2L.Bmx/AwsCredsCreator.cs index 56019f1e..fb687aaa 100644 --- a/src/D2L.Bmx/AwsCredsCreator.cs +++ b/src/D2L.Bmx/AwsCredsCreator.cs @@ -13,6 +13,7 @@ AwsCredentials Credentials internal class AwsCredsCreator( IAwsClient awsClient, IConsolePrompter consolePrompter, + IConsoleWriter consoleWriter, IAwsCredentialCache awsCredentialCache, BmxConfig config ) { @@ -30,12 +31,22 @@ bool cache ); } + var accountSource = ParameterSource.CliArg; if( string.IsNullOrEmpty( account ) && !string.IsNullOrEmpty( config.Account ) ) { account = config.Account; + accountSource = ParameterSource.Config; + } + if( !string.IsNullOrEmpty( account ) && !nonInteractive ) { + consoleWriter.WriteParameter( ParameterDescriptions.Account, account, accountSource ); } + var roleSource = ParameterSource.CliArg; if( string.IsNullOrEmpty( role ) && !string.IsNullOrEmpty( config.Role ) ) { role = config.Role; + roleSource = ParameterSource.Config; + } + if( !string.IsNullOrEmpty( role ) && !nonInteractive ) { + consoleWriter.WriteParameter( ParameterDescriptions.Role, role, roleSource ); } if( duration is null or 0 ) { diff --git a/src/D2L.Bmx/ConsolePrompter.cs b/src/D2L.Bmx/ConsolePrompter.cs index 8412148e..49c281a0 100644 --- a/src/D2L.Bmx/ConsolePrompter.cs +++ b/src/D2L.Bmx/ConsolePrompter.cs @@ -8,7 +8,7 @@ internal interface IConsolePrompter { string PromptOrg( bool allowEmptyInput ); string PromptProfile(); string PromptUser( bool allowEmptyInput ); - string PromptPassword( string user, string org ); + string PromptPassword(); int? PromptDuration(); string PromptAccount( string[] accounts ); string PromptRole( string[] roles ); @@ -63,9 +63,7 @@ string IConsolePrompter.PromptUser( bool allowEmptyInput ) { return user; } - string IConsolePrompter.PromptPassword( string user, string org ) { - Console.Error.WriteLine( $"{ParameterDescriptions.Org}: {org}" ); - Console.Error.WriteLine( $"{ParameterDescriptions.User}: {user}" ); + string IConsolePrompter.PromptPassword() { return GetMaskedInput( $"{ParameterDescriptions.Password}: " ); } diff --git a/src/D2L.Bmx/ConsoleWriter.cs b/src/D2L.Bmx/ConsoleWriter.cs new file mode 100644 index 00000000..7f0ce726 --- /dev/null +++ b/src/D2L.Bmx/ConsoleWriter.cs @@ -0,0 +1,17 @@ +namespace D2L.Bmx; + +internal interface IConsoleWriter { + void WriteParameter( string description, string value, ParameterSource source ); +} + +internal class ConsoleWriter : IConsoleWriter { + void IConsoleWriter.WriteParameter( string description, string value, ParameterSource source ) { + Console.ResetColor(); + Console.Error.Write( $"{description}: " ); + Console.ForegroundColor = ConsoleColor.Cyan; + Console.Error.Write( value ); + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.Error.WriteLine( $" (from {source})" ); + Console.ResetColor(); + } +} diff --git a/src/D2L.Bmx/OktaAuthenticator.cs b/src/D2L.Bmx/OktaAuthenticator.cs index d459266d..b82ae026 100644 --- a/src/D2L.Bmx/OktaAuthenticator.cs +++ b/src/D2L.Bmx/OktaAuthenticator.cs @@ -13,6 +13,7 @@ internal class OktaAuthenticator( IOktaApi oktaApi, IOktaSessionStorage sessionStorage, IConsolePrompter consolePrompter, + IConsoleWriter consoleWriter, BmxConfig config ) { public async Task AuthenticateAsync( @@ -21,24 +22,32 @@ public async Task AuthenticateAsync( bool nonInteractive, bool ignoreCache ) { + var orgSource = ParameterSource.CliArg; + if( string.IsNullOrEmpty( org ) && !string.IsNullOrEmpty( config.Org ) ) { + org = config.Org; + orgSource = ParameterSource.Config; + } if( string.IsNullOrEmpty( org ) ) { - if( !string.IsNullOrEmpty( config.Org ) ) { - org = config.Org; - } else if( !nonInteractive ) { - org = consolePrompter.PromptOrg( allowEmptyInput: false ); - } else { + if( nonInteractive ) { throw new BmxException( "Org value was not provided" ); } + org = consolePrompter.PromptOrg( allowEmptyInput: false ); + } else if( !nonInteractive ) { + consoleWriter.WriteParameter( ParameterDescriptions.Org, org, orgSource ); } + var userSource = ParameterSource.CliArg; + if( string.IsNullOrEmpty( user ) && !string.IsNullOrEmpty( config.User ) ) { + user = config.User; + userSource = ParameterSource.Config; + } if( string.IsNullOrEmpty( user ) ) { - if( !string.IsNullOrEmpty( config.User ) ) { - user = config.User; - } else if( !nonInteractive ) { - user = consolePrompter.PromptUser( allowEmptyInput: false ); - } else { + if( nonInteractive ) { throw new BmxException( "User value was not provided" ); } + user = consolePrompter.PromptUser( allowEmptyInput: false ); + } else if( !nonInteractive ) { + consoleWriter.WriteParameter( ParameterDescriptions.User, user, userSource ); } oktaApi.SetOrganization( org ); @@ -50,7 +59,7 @@ bool ignoreCache throw new BmxException( "Okta authentication failed. Please run `bmx login` first." ); } - string password = consolePrompter.PromptPassword( user, org ); + string password = consolePrompter.PromptPassword(); var authnResponse = await oktaApi.AuthenticateAsync( user, password ); diff --git a/src/D2L.Bmx/ParameterDescriptions.cs b/src/D2L.Bmx/ParameterDescriptions.cs index 726df5e5..be7f4bc9 100644 --- a/src/D2L.Bmx/ParameterDescriptions.cs +++ b/src/D2L.Bmx/ParameterDescriptions.cs @@ -1,7 +1,7 @@ namespace D2L.Bmx; internal static class ParameterDescriptions { - public const string Org = "Okta org short name or domain name"; + public const string Org = "Okta org or domain name"; public const string User = "Okta username"; public const string Password = "Okta password"; public const string Account = "AWS account name"; diff --git a/src/D2L.Bmx/ParameterSource.cs b/src/D2L.Bmx/ParameterSource.cs new file mode 100644 index 00000000..375cc697 --- /dev/null +++ b/src/D2L.Bmx/ParameterSource.cs @@ -0,0 +1,14 @@ +namespace D2L.Bmx; + +internal record ParameterSource { + public string Description { get; init; } + + private ParameterSource( string description ) { + Description = description; + } + + public static ParameterSource CliArg => new( "command line argument" ); + public static ParameterSource Config => new( "config file" ); + + public override string ToString() => Description; +} diff --git a/src/D2L.Bmx/Program.cs b/src/D2L.Bmx/Program.cs index d17bf61b..1b4b8065 100644 --- a/src/D2L.Bmx/Program.cs +++ b/src/D2L.Bmx/Program.cs @@ -29,6 +29,7 @@ new OktaApi(), new OktaSessionStorage(), new ConsolePrompter(), + new ConsoleWriter(), config ) ); return handler.HandleAsync( @@ -121,16 +122,19 @@ printCommand.SetHandler( ( InvocationContext context ) => { var consolePrompter = new ConsolePrompter(); + var consoleWriter = new ConsoleWriter(); var config = new BmxConfigProvider( new FileIniDataParser() ).GetConfiguration(); var handler = new PrintHandler( new OktaAuthenticator( new OktaApi(), new OktaSessionStorage(), consolePrompter, + consoleWriter, config ), new AwsCredsCreator( new AwsClient( new AmazonSecurityTokenServiceClient( new AnonymousAWSCredentials() ) ), consolePrompter, + consoleWriter, new AwsCredsCache(), config ) ); @@ -172,19 +176,23 @@ writeCommand.SetHandler( ( InvocationContext context ) => { var consolePrompter = new ConsolePrompter(); + var consoleWriter = new ConsoleWriter(); var config = new BmxConfigProvider( new FileIniDataParser() ).GetConfiguration(); var handler = new WriteHandler( new OktaAuthenticator( new OktaApi(), new OktaSessionStorage(), consolePrompter, + consoleWriter, config ), new AwsCredsCreator( new AwsClient( new AmazonSecurityTokenServiceClient( new AnonymousAWSCredentials() ) ), consolePrompter, + consoleWriter, new AwsCredsCache(), config ), consolePrompter, + consoleWriter, config, new FileIniDataParser() ); diff --git a/src/D2L.Bmx/WriteHandler.cs b/src/D2L.Bmx/WriteHandler.cs index f4f4521d..6c4886a0 100644 --- a/src/D2L.Bmx/WriteHandler.cs +++ b/src/D2L.Bmx/WriteHandler.cs @@ -10,6 +10,7 @@ internal class WriteHandler( OktaAuthenticator oktaAuth, AwsCredsCreator awsCredsCreator, IConsolePrompter consolePrompter, + IConsoleWriter consoleWriter, BmxConfig config, FileIniDataParser parser ) { @@ -45,12 +46,18 @@ bool useCredentialProcess cache: cacheAwsCredentials ); + var profileSource = ParameterSource.CliArg; + if( string.IsNullOrEmpty( profile ) && !string.IsNullOrEmpty( config.Profile ) ) { + profile = config.Profile; + profileSource = ParameterSource.Config; + } if( string.IsNullOrEmpty( profile ) ) { - if( !string.IsNullOrEmpty( config.Profile ) ) { - profile = config.Profile; - } else { - profile = consolePrompter.PromptProfile(); + if( nonInteractive ) { + throw new BmxException( "Profile value was not provided" ); } + profile = consolePrompter.PromptProfile(); + } else if( !nonInteractive ) { + consoleWriter.WriteParameter( ParameterDescriptions.Profile, profile, profileSource ); } if( !string.IsNullOrEmpty( output ) && !Path.IsPathRooted( output ) ) {