Skip to content

Commit c14de8e

Browse files
committed
add passwordless option that defaults to false
1 parent aedb835 commit c14de8e

11 files changed

+87
-19
lines changed

src/D2L.Bmx/BmxConfig.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ internal record BmxConfig(
66
string? Account,
77
string? Role,
88
string? Profile,
9-
int? Duration
9+
int? Duration,
10+
bool? Passwordless
1011
);

src/D2L.Bmx/BmxConfigProvider.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,22 @@ public BmxConfig GetConfiguration() {
4444
duration = configDuration;
4545
}
4646

47+
bool? passwordless = null;
48+
if( !string.IsNullOrEmpty( data.Global["passwordless"] ) ) {
49+
if( !bool.TryParse( data.Global["passwordless"], out bool configPasswordless ) ) {
50+
throw new BmxException( "Invalid passwordless in config" );
51+
}
52+
passwordless = configPasswordless;
53+
}
54+
4755
return new BmxConfig(
4856
Org: data.Global["org"],
4957
User: data.Global["user"],
5058
Account: data.Global["account"],
5159
Role: data.Global["role"],
5260
Profile: data.Global["profile"],
53-
Duration: duration
61+
Duration: duration,
62+
Passwordless: passwordless
5463
);
5564
}
5665

@@ -75,6 +84,9 @@ public void SaveConfiguration( BmxConfig config ) {
7584
if( config.Duration.HasValue ) {
7685
data.Global["duration"] = $"{config.Duration}";
7786
}
87+
if( config.Passwordless.HasValue ) {
88+
data.Global["passwordless"] = $"{config.Passwordless}";
89+
}
7890

7991
fs.Position = 0;
8092
fs.SetLength( 0 );

src/D2L.Bmx/Browser.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class Browser {
3232
}
3333

3434
var launchOptions = new LaunchOptions {
35-
Headless = true,
35+
Headless = false,
3636
ExecutablePath = browserPath,
3737
Args = noSandbox ? ["--no-sandbox"] : []
3838
};

src/D2L.Bmx/ConfigureHandler.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ public void Handle(
99
string? org,
1010
string? user,
1111
int? duration,
12-
bool nonInteractive
12+
bool nonInteractive,
13+
bool? passwordless
1314
) {
1415

1516
if( string.IsNullOrEmpty( org ) && !nonInteractive ) {
@@ -24,13 +25,18 @@ bool nonInteractive
2425
duration = consolePrompter.PromptDuration();
2526
}
2627

28+
if( passwordless is null && !nonInteractive ) {
29+
passwordless = consolePrompter.PromptPasswordless();
30+
}
31+
2732
BmxConfig config = new(
2833
Org: org,
2934
User: user,
3035
Account: null,
3136
Role: null,
3237
Profile: null,
33-
Duration: duration
38+
Duration: duration,
39+
Passwordless: passwordless
3440
);
3541
configProvider.SaveConfiguration( config );
3642
Console.WriteLine( "Your configuration has been created. Okta sessions will now also be cached." );

src/D2L.Bmx/ConsolePrompter.cs

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ internal interface IConsolePrompter {
1313
string PromptRole( string[] roles );
1414
OktaMfaFactor SelectMfa( OktaMfaFactor[] mfaOptions );
1515
string GetMfaResponse( string mfaInputPrompt, bool maskInput );
16+
bool PromptPasswordless();
1617
}
1718

1819
internal class ConsolePrompter : IConsolePrompter {
@@ -107,6 +108,15 @@ string IConsolePrompter.PromptRole( string[] roles ) {
107108
return roles[index - 1];
108109
}
109110

111+
bool IConsolePrompter.PromptPasswordless() {
112+
Console.Error.Write( $"{ParameterDescriptions.Passwordless} (y/n): " );
113+
string? input = Console.ReadLine();
114+
if( input is null || input.Length != 1 || ( input[0] != 'y' && input[0] != 'n' ) ) {
115+
throw new BmxException( "Invalid passwordless input" );
116+
}
117+
return input[0] == 'y';
118+
}
119+
110120
OktaMfaFactor IConsolePrompter.SelectMfa( OktaMfaFactor[] mfaOptions ) {
111121
Console.Error.WriteLine( "MFA Required" );
112122

src/D2L.Bmx/LoginHandler.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@ OktaAuthenticator oktaAuth
66
public async Task HandleAsync(
77
string? org,
88
string? user,
9-
bool experimental
9+
bool experimental,
10+
bool? passwordless
1011
) {
1112
if( !File.Exists( BmxPaths.CONFIG_FILE_NAME ) ) {
1213
throw new BmxException(
1314
"BMX global config file not found. Okta sessions will not be saved. Please run `bmx configure` first."
1415
);
1516
}
16-
await oktaAuth.AuthenticateAsync( org, user, nonInteractive: false, ignoreCache: true, experimental: experimental );
17+
await oktaAuth.AuthenticateAsync(
18+
org,
19+
user,
20+
nonInteractive: false,
21+
ignoreCache: true,
22+
experimental: experimental,
23+
passwordless: passwordless
24+
);
1725
Console.WriteLine( "Successfully logged in and Okta session has been cached." );
1826
}
1927
}

src/D2L.Bmx/OktaAuthenticator.cs

+17-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public async Task<OktaAuthenticatedContext> AuthenticateAsync(
2525
string? user,
2626
bool nonInteractive,
2727
bool ignoreCache,
28-
bool experimental
28+
bool experimental,
29+
bool? passwordless
2930
) {
3031
var orgSource = ParameterSource.CliArg;
3132
if( string.IsNullOrEmpty( org ) && !string.IsNullOrEmpty( config.Org ) ) {
@@ -55,12 +56,18 @@ bool experimental
5556
consoleWriter.WriteParameter( ParameterDescriptions.User, user, userSource );
5657
}
5758

59+
if( passwordless is null && config.Passwordless is not null ) {
60+
passwordless = config.Passwordless;
61+
}
62+
5863
var oktaAnonymous = oktaClientFactory.CreateAnonymousClient( org );
5964

6065
if( !ignoreCache && TryAuthenticateFromCache( org, user, oktaClientFactory, out var oktaAuthenticated ) ) {
6166
return new OktaAuthenticatedContext( Org: org, User: user, Client: oktaAuthenticated );
6267
}
63-
if( await TryAuthenticateWithDSSOAsync( org, user, oktaClientFactory, experimental ) is { } oktaDSSOAuthenticated ) {
68+
if( passwordless == true
69+
&& await TryAuthenticateWithDSSOAsync( org, user, oktaClientFactory, experimental ) is { } oktaDSSOAuthenticated
70+
) {
6471
return new OktaAuthenticatedContext( Org: org, User: user, Client: oktaDSSOAuthenticated );
6572
}
6673
if( nonInteractive ) {
@@ -230,7 +237,14 @@ IResponse response
230237

231238
var oktaAuthenticatedClient = oktaClientFactory.CreateAuthenticatedClient( org, sessionId );
232239
var sessionExpiry = await oktaAuthenticatedClient.GetSessionExpiryAsync();
233-
CacheOktaSession( user, org, sessionId, sessionExpiry );
240+
if( File.Exists( BmxPaths.CONFIG_FILE_NAME ) ) {
241+
CacheOktaSession( user, org, sessionId, sessionExpiry );
242+
} else {
243+
consoleWriter.WriteWarning( """
244+
No config file found. Your Okta session will not be cached.
245+
Consider running `bmx configure` if you own this machine.
246+
""" );
247+
}
234248
return oktaAuthenticatedClient;
235249
}
236250

src/D2L.Bmx/ParameterDescriptions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ internal static class ParameterDescriptions {
2020
""";
2121

2222
public const string Experimental = "Enables experimental features";
23+
public const string Passwordless = "Use Okta DSSO to attempt to authenticate without providing a password";
2324
}

src/D2L.Bmx/PrintHandler.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ public async Task HandleAsync(
1515
bool nonInteractive,
1616
string? format,
1717
bool cacheAwsCredentials,
18-
bool experimental
18+
bool experimental,
19+
bool? passwordless
1920
) {
2021
var oktaContext = await oktaAuth.AuthenticateAsync(
2122
org: org,
2223
user: user,
2324
nonInteractive: nonInteractive,
2425
ignoreCache: false,
25-
experimental: experimental
26+
experimental: experimental,
27+
passwordless: passwordless
2628
);
2729
var awsCreds = ( await awsCredsCreator.CreateAwsCredsAsync(
2830
okta: oktaContext,

src/D2L.Bmx/Program.cs

+17-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
var userOption = new Option<string>(
1818
name: "--user",
1919
description: ParameterDescriptions.User );
20+
var passwordlessOption = new Option<bool?>(
21+
name: "--passwordless",
22+
description: ParameterDescriptions.Passwordless
23+
);
2024

2125
// allow no-sandbox argument for DSSO and future experimental features
2226
var experimentalOption = new Option<bool>(
@@ -28,6 +32,7 @@
2832
orgOption,
2933
userOption,
3034
experimentalOption,
35+
passwordlessOption
3136
};
3237
loginCommand.SetHandler( ( InvocationContext context ) => {
3338
var consoleWriter = new ConsoleWriter();
@@ -42,7 +47,8 @@
4247
return handler.HandleAsync(
4348
org: context.ParseResult.GetValueForOption( orgOption ),
4449
user: context.ParseResult.GetValueForOption( userOption ),
45-
experimental: context.ParseResult.GetValueForOption( experimentalOption )
50+
experimental: context.ParseResult.GetValueForOption( experimentalOption ),
51+
passwordless: context.ParseResult.GetValueForOption( passwordlessOption )
4652
);
4753
} );
4854

@@ -71,6 +77,7 @@
7177
userOption,
7278
durationOption,
7379
nonInteractiveOption,
80+
passwordlessOption,
7481
};
7582

7683
configureCommand.SetHandler( ( InvocationContext context ) => {
@@ -81,7 +88,8 @@
8188
org: context.ParseResult.GetValueForOption( orgOption ),
8289
user: context.ParseResult.GetValueForOption( userOption ),
8390
duration: context.ParseResult.GetValueForOption( durationOption ),
84-
nonInteractive: context.ParseResult.GetValueForOption( nonInteractiveOption )
91+
nonInteractive: context.ParseResult.GetValueForOption( nonInteractiveOption ),
92+
passwordless: context.ParseResult.GetValueForOption( passwordlessOption )
8593
);
8694
return Task.CompletedTask;
8795
} );
@@ -127,6 +135,7 @@
127135
nonInteractiveOption,
128136
cacheAwsCredentialsOption,
129137
experimentalOption,
138+
passwordlessOption,
130139
};
131140

132141
printCommand.SetHandler( ( InvocationContext context ) => {
@@ -156,7 +165,8 @@
156165
nonInteractive: context.ParseResult.GetValueForOption( nonInteractiveOption ),
157166
format: context.ParseResult.GetValueForOption( formatOption ),
158167
cacheAwsCredentials: context.ParseResult.GetValueForOption( cacheAwsCredentialsOption ),
159-
experimental: context.ParseResult.GetValueForOption( experimentalOption )
168+
experimental: context.ParseResult.GetValueForOption( experimentalOption ),
169+
passwordless: context.ParseResult.GetValueForOption( passwordlessOption )
160170
);
161171
} );
162172

@@ -182,7 +192,8 @@
182192
nonInteractiveOption,
183193
cacheAwsCredentialsOption,
184194
useCredentialProcessOption,
185-
experimentalOption
195+
experimentalOption,
196+
passwordlessOption,
186197
};
187198

188199
writeCommand.SetHandler( ( InvocationContext context ) => {
@@ -218,7 +229,8 @@
218229
profile: context.ParseResult.GetValueForOption( profileOption ),
219230
cacheAwsCredentials: context.ParseResult.GetValueForOption( cacheAwsCredentialsOption ),
220231
useCredentialProcess: context.ParseResult.GetValueForOption( useCredentialProcessOption ),
221-
experimental: context.ParseResult.GetValueForOption( experimentalOption )
232+
experimental: context.ParseResult.GetValueForOption( experimentalOption ),
233+
passwordless: context.ParseResult.GetValueForOption( passwordlessOption )
222234
);
223235
} );
224236

src/D2L.Bmx/WriteHandler.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public async Task HandleAsync(
2828
string? profile,
2929
bool cacheAwsCredentials,
3030
bool useCredentialProcess,
31-
bool experimental
31+
bool experimental,
32+
bool? passwordless
3233
) {
3334
cacheAwsCredentials = cacheAwsCredentials || useCredentialProcess;
3435

@@ -37,7 +38,8 @@ bool experimental
3738
user: user,
3839
nonInteractive: nonInteractive,
3940
ignoreCache: false,
40-
experimental: experimental
41+
experimental: experimental,
42+
passwordless: passwordless
4143
);
4244
var awsCredsInfo = await awsCredsCreator.CreateAwsCredsAsync(
4345
okta: oktaContext,

0 commit comments

Comments
 (0)