Skip to content

Commit

Permalink
De-serialize Security Question MFA to different type
Browse files Browse the repository at this point in the history
  • Loading branch information
boarnoah committed Jul 8, 2024
1 parent 28098e6 commit 7e9e4db
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 27 deletions.
4 changes: 2 additions & 2 deletions src/D2L.Bmx/ConsolePrompter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,12 @@ OktaMfaFactor IConsolePrompter.SelectMfa( OktaMfaFactor[] mfaOptions ) {
}

if( mfaOptions.Length == 1 ) {
Console.Error.WriteLine( $"MFA method: {mfaOptions[0].Provider}-{mfaOptions[0].FactorType}" );
Console.Error.WriteLine( $"MFA method: {mfaOptions[0].Provider}-{mfaOptions[0].FactorName}" );
return mfaOptions[0];
}

for( int i = 0; i < mfaOptions.Length; i++ ) {
Console.Error.WriteLine( $"[{i + 1}] {mfaOptions[i].Provider}-{mfaOptions[i].FactorType}" );
Console.Error.WriteLine( $"[{i + 1}] {mfaOptions[i].Provider}-{mfaOptions[i].FactorName}" );
}
Console.Error.Write( "Select an available MFA option: " );
if( !int.TryParse( _stdinReader.ReadLine(), out int index ) || index > mfaOptions.Length || index < 1 ) {
Expand Down
1 change: 1 addition & 0 deletions src/D2L.Bmx/D2L.Bmx.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<PackageReference Include="HtmlAgilityPack" Version="1.11.57" />
<PackageReference Include="ini-parser-netstandard" Version="2.5.2" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.Text.Json" Version="9.0.0-preview.5.24306.7" />
</ItemGroup>

</Project>
5 changes: 4 additions & 1 deletion src/D2L.Bmx/JsonSerializerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

namespace D2L.Bmx;

[JsonSourceGenerationOptions( PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase )]
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
AllowOutOfOrderMetadataProperties = true
)]
[JsonSerializable( typeof( AuthenticateRequest ) )]
[JsonSerializable( typeof( IssueMfaChallengeRequest ) )]
[JsonSerializable( typeof( VerifyMfaChallengeResponseRequest ) )]
Expand Down
65 changes: 58 additions & 7 deletions src/D2L.Bmx/Okta/Models/AuthenticateResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,68 @@ internal record AuthenticateResponseEmbedded(
OktaMfaFactor[]? Factors
);

[JsonPolymorphic(
TypeDiscriminatorPropertyName = "factorType",
IgnoreUnrecognizedTypeDiscriminators = true
)]
[JsonDerivedType( typeof( OktaMfaQuestionFactor ), "question" )]
[JsonDerivedType( typeof( OktaMfaHardwareToken ), "token" )]
[JsonDerivedType( typeof( OktaMfaHardwareTokenFactor ), "token:hardware" )]
[JsonDerivedType( typeof( OktaMfaSoftwareTotpFactor ), "token:software:totp" )]
[JsonDerivedType( typeof( OktaMfaHotpFactor ), "token:hotp" )]
[JsonDerivedType( typeof( OktaMfaSmsFactor ), "sms" )]
[JsonDerivedType( typeof( OktaMfaCallFactor ), "call" )]
[JsonDerivedType( typeof( OktaMfaEmailFactor ), "email" )]
internal record OktaMfaFactor {
public required string Id { get; set; }
public required string Provider { get; set; }
public required string VendorName { get; set; }

[JsonIgnore]
public virtual string FactorName => "Unknown";
[JsonIgnore]
public virtual bool RequireChallengeIssue => false;
}

internal record OktaMfaQuestionFactor() : OktaMfaFactor() {
public override string FactorName => "Security Question";
public required OktaMfaQuestionProfile Profile { get; set; }
}

internal record OktaMfaQuestionProfile(
string QuestionText
);

internal record OktaMfaFactor(
string Id,
string FactorType,
string Provider,
string VendorName,
OktaMfaQuestionProfile Profile
);
internal record OktaMfaHardwareToken() : OktaMfaFactor {
public override string FactorName => "Hardware Token";
}

internal record OktaMfaHardwareTokenFactor() : OktaMfaFactor {
public override string FactorName => "Hardware TOTP";
}

internal record OktaMfaSoftwareTotpFactor() : OktaMfaFactor {
public override string FactorName => "Software TOTP";
}

internal record OktaMfaHotpFactor() : OktaMfaFactor {
public override string FactorName => "HOTP";
}

internal record OktaMfaSmsFactor() : OktaMfaFactor {
public override string FactorName => "SMS";
public override bool RequireChallengeIssue => true;
}

internal record OktaMfaCallFactor() : OktaMfaFactor {
public override string FactorName => "Call";
public override bool RequireChallengeIssue => true;
}

internal record OktaMfaEmailFactor() : OktaMfaFactor {
public override string FactorName => "Email";
public override bool RequireChallengeIssue => true;
}

internal enum AuthenticationStatus {
UNKNOWN,
Expand Down
21 changes: 4 additions & 17 deletions src/D2L.Bmx/OktaAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,18 @@ bool ignoreCache
if( authnResponse is AuthenticateResponse.MfaRequired mfaInfo ) {
OktaMfaFactor mfaFactor = consolePrompter.SelectMfa( mfaInfo.Factors );

if( !IsMfaFactorTypeSupported( mfaFactor.FactorType ) ) {
if( mfaFactor.FactorName == "Unknown" ) {
throw new BmxException( "Selected MFA not supported by BMX" );
}

// TODO: Handle retry
if( mfaFactor.FactorType is "sms" or "call" or "email" ) {
if( mfaFactor.RequireChallengeIssue ) {
await oktaApi.IssueMfaChallengeAsync( mfaInfo.StateToken, mfaFactor.Id );
}

string mfaResponse = consolePrompter.GetMfaResponse(
mfaFactor.FactorType == "question" ? mfaFactor.Profile.QuestionText : "PassCode",
mfaFactor.FactorType == "question"
mfaFactor is OktaMfaQuestionFactor questionFactor ? questionFactor.Profile.QuestionText : "PassCode",
mfaFactor is OktaMfaQuestionFactor // Security question factor is a static value
);

authnResponse = await oktaApi.VerifyMfaChallengeResponseAsync( mfaInfo.StateToken, mfaFactor.Id, mfaResponse );
Expand Down Expand Up @@ -133,17 +133,4 @@ private List<OktaSessionCache> ReadOktaSessionCacheFile() {
var currTime = DateTimeOffset.Now;
return sourceCache.Where( session => session.ExpiresAt > currTime ).ToList();
}

private static bool IsMfaFactorTypeSupported( string mfaFactorType ) {
return mfaFactorType is
"call"
or "email"
or "question"
or "sms"
or "token:hardware"
or "token:hotp"
or "token:software:totp"
or "token";
}

}

0 comments on commit 7e9e4db

Please sign in to comment.