1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-17 00:33:35 +00:00

added support additional two factor providers during login

This commit is contained in:
Kyle Spearrin
2017-08-15 14:32:40 -04:00
parent 9803a55ca3
commit 519171d241
9 changed files with 166 additions and 37 deletions

View File

@@ -1,19 +1,16 @@
using Bit.Core.Models;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Services;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using Con = System.Console;
namespace Bit.Console
{
class Program
public class Program
{
private static bool _usingArgs = false;
private static bool _exit = false;
@@ -155,6 +152,7 @@ namespace Bit.Console
string masterPassword = null;
string token = null;
string orgId = null;
var provider = TwoFactorProviderType.Authenticator;
if(_usingArgs)
{
@@ -164,7 +162,8 @@ namespace Bit.Console
email = parameters["e"];
masterPassword = parameters["p"];
}
if(parameters.Count >= 3 && parameters.ContainsKey("t"))
if(parameters.Count >= 4 && parameters.ContainsKey("t") && parameters.ContainsKey("tp") &&
Enum.TryParse(parameters["tp"], out provider))
{
token = parameters["t"];
}
@@ -198,21 +197,81 @@ namespace Bit.Console
}
else
{
result = await AuthService.Instance.LogInTwoFactorAsync(email, masterPassword, token);
result = await AuthService.Instance.LogInTwoFactorAsync(provider, token,
email, masterPassword);
}
if(string.IsNullOrWhiteSpace(token) && result.TwoFactorRequired)
{
Con.WriteLine();
Con.WriteLine();
Con.WriteLine("Two-step login is enabled on this account. Please enter your verification code.");
Con.Write("Verification code: ");
token = Con.ReadLine().Trim();
result = await AuthService.Instance.LogInTwoFactorWithHashAsync(token, email,
result.MasterPasswordHash);
Con.WriteLine("Two-step login is enabled on this account.");
if(result.TwoFactorProviders.Count > 1)
{
for(var i = 0; i < result.TwoFactorProviders.Count; i++)
{
Con.WriteLine("{0}. {1}{2}", i + 1, result.TwoFactorProviders.ElementAt(i).Key,
result.TwoFactorProviders.ElementAt(i).Key == TwoFactorProviderType.Duo ||
result.TwoFactorProviders.ElementAt(i).Key == TwoFactorProviderType.U2f ?
" - not supported" : string.Empty);
}
Con.WriteLine();
Con.Write("Which provider would you like to use?: ");
var providerIndexInput = Con.ReadLine().Trim();
int providerIndex;
if(int.TryParse(providerIndexInput, out providerIndex) && result.TwoFactorProviders.Count >= providerIndex)
{
provider = result.TwoFactorProviders.ElementAt(providerIndex - 1).Key;
}
else
{
provider = result.TwoFactorProviders.First().Key;
}
}
else
{
provider = result.TwoFactorProviders.First().Key;
Con.WriteLine();
}
if(result.Success && result.Organizations.Count > 1)
var readingTokenInput = true;
if(provider == TwoFactorProviderType.YubiKey)
{
Con.Write("Tap the button on your YubiKey: ");
}
else if(provider == TwoFactorProviderType.Email)
{
if(result.TwoFactorProviders.Count > 1)
{
await ApiService.Instance.PostTwoFactorSendEmailLoginAsync(new TwoFactorEmailRequest
{
Email = email,
MasterPasswordHash = result.MasterPasswordHash
});
}
Con.Write("Enter the verification code that was emailed to you: ");
}
else if(provider == TwoFactorProviderType.Authenticator)
{
Con.Write("Enter the verification code from your authenticator app: ");
}
else
{
Con.WriteLine("The selected two-step login method is not supported on this platform/application. " +
"Use a different two step-login method.");
readingTokenInput = false;
}
if(readingTokenInput)
{
token = Con.ReadLine().Trim();
result = await AuthService.Instance.LogInTwoFactorWithHashAsync(provider, token, email,
result.MasterPasswordHash);
}
}
if(result.Success && result.Organizations != null && result.Organizations.Count > 1)
{
Organization org = null;
if(!string.IsNullOrWhiteSpace(orgId))
@@ -252,11 +311,18 @@ namespace Bit.Console
Con.WriteLine();
Con.WriteLine();
if(result.Success)
{
if(!result.TwoFactorRequired)
{
WriteSuccessLine(string.Format("You have successfully logged in as {0}!",
TokenService.Instance.AccessTokenEmail));
}
else
{
WriteErrorLine("Unable to log in.");
}
}
else
{
WriteErrorLine(result.ErrorMessage);
}

View File

@@ -87,6 +87,7 @@
<Compile Include="Enums\DirectoryType.cs" />
<Compile Include="Enums\OrganizationUserType.cs" />
<Compile Include="Enums\OrganizationUserStatusType.cs" />
<Compile Include="Enums\TwoFactorProviderType.cs" />
<Compile Include="Enums\UserAccountControl.cs" />
<Compile Include="Installer.cs" />
<Compile Include="Models\ApiError.cs" />
@@ -107,6 +108,7 @@
<Compile Include="Models\TokenRequest.cs" />
<Compile Include="Models\ProfileResponse.cs" />
<Compile Include="Models\TokenResponse.cs" />
<Compile Include="Models\TwoFactorEmailRequest.cs" />
<Compile Include="Services\ApiService.cs" />
<Compile Include="Services\GSuiteDirectoryService.cs" />
<Compile Include="Services\ControllerService.cs" />

View File

@@ -0,0 +1,12 @@
namespace Bit.Core.Enums
{
public enum TwoFactorProviderType : byte
{
Authenticator = 0,
Email = 1,
Duo = 2,
YubiKey = 3,
U2f = 4,
Remember = 5
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -10,7 +11,8 @@ namespace Bit.Core.Models
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
public bool TwoFactorRequired { get; set; }
public bool TwoFactorRequired => TwoFactorProviders != null && TwoFactorProviders.Count > 0;
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
public string MasterPasswordHash { get; set; }
public List<Organization> Organizations { get; set; }
}

View File

@@ -1,4 +1,5 @@
using System;
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
namespace Bit.Core.Models
@@ -8,7 +9,8 @@ namespace Bit.Core.Models
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
public string Token { get; set; }
public int? Provider { get; set; }
public TwoFactorProviderType? Provider { get; set; }
public bool Remember { get; set; }
public IDictionary<string, string> ToIdentityTokenRequest()
{
@@ -24,7 +26,8 @@ namespace Bit.Core.Models
if(Token != null && Provider.HasValue)
{
dict.Add("TwoFactorToken", Token);
dict.Add("TwoFactorProvider", Provider.Value.ToString());
dict.Add("TwoFactorProvider", ((byte)(Provider.Value)).ToString());
dict.Add("TwoFactorRemember", Remember ? "1" : "0");
}
return dict;

View File

@@ -1,9 +1,6 @@
using Newtonsoft.Json;
using System;
using Bit.Core.Enums;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bit.Core.Models
{
@@ -17,7 +14,9 @@ namespace Bit.Core.Models
public string RefreshToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
public List<int> TwoFactorProviders { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders2 { get; set; }
public string PrivateKey { get; set; }
public string TwoFactorToken { get; set; }
public string Key { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace Bit.Core.Models
{
public class TwoFactorEmailRequest
{
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.Models;
using Bit.Core.Enums;
using Bit.Core.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@@ -51,11 +52,12 @@ namespace Bit.Core.Services
if(!response.IsSuccessStatusCode)
{
var errorResponse = JObject.Parse(responseContent);
if(errorResponse["TwoFactorProviders"] != null)
if(errorResponse["TwoFactorProviders2"] != null)
{
return ApiResult<TokenResponse>.Success(new TokenResponse
{
TwoFactorProviders = errorResponse["TwoFactorProviders"].ToObject<List<int>>()
TwoFactorProviders2 = errorResponse["TwoFactorProviders2"]
.ToObject<Dictionary<TwoFactorProviderType, Dictionary<string, object>>>()
}, response.StatusCode);
}
@@ -140,6 +142,33 @@ namespace Bit.Core.Services
}
}
public virtual async Task<ApiResult> PostTwoFactorSendEmailLoginAsync(TwoFactorEmailRequest requestObj)
{
var stringContent = JsonConvert.SerializeObject(requestObj);
var requestMessage = new HttpRequestMessage()
{
Method = HttpMethod.Post,
RequestUri = new Uri(string.Concat(SettingsService.Instance.ApiBaseUrl, "/two-factor/send-email-login")),
Content = new StringContent(stringContent, Encoding.UTF8, "application/json")
};
try
{
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync(response).ConfigureAwait(false);
}
return ApiResult.Success(response.StatusCode);
}
catch
{
return HandledWebException();
}
}
protected ApiResult HandledWebException()
{
return ApiResult.Failed(HttpStatusCode.BadGateway,

View File

@@ -63,9 +63,9 @@ namespace Bit.Core.Services
}
result.Success = true;
if(response.Result.TwoFactorProviders != null && response.Result.TwoFactorProviders.Count > 0)
if(response.Result.TwoFactorProviders2 != null && response.Result.TwoFactorProviders2.Count > 0)
{
result.TwoFactorRequired = true;
result.TwoFactorProviders = response.Result.TwoFactorProviders2;
result.MasterPasswordHash = request.MasterPasswordHash;
return result;
}
@@ -73,12 +73,13 @@ namespace Bit.Core.Services
return await ProcessLogInSuccessAsync(response.Result);
}
public async Task<LoginResult> LogInTwoFactorAsync(string token, string email, string masterPassword)
public async Task<LoginResult> LogInTwoFactorAsync(TwoFactorProviderType type, string token, string email,
string masterPassword)
{
var normalizedEmail = email.Trim().ToLower();
var key = Crypto.MakeKeyFromPassword(masterPassword, normalizedEmail);
var result = await LogInTwoFactorWithHashAsync(token, email, Crypto.HashPasswordBase64(key, masterPassword));
var result = await LogInTwoFactorWithHashAsync(type, token, email, Crypto.HashPasswordBase64(key, masterPassword));
key = null;
masterPassword = null;
@@ -86,14 +87,21 @@ namespace Bit.Core.Services
return result;
}
public async Task<LoginResult> LogInTwoFactorWithHashAsync(string token, string email, string masterPasswordHash)
public async Task<LoginResult> LogInTwoFactorWithHashAsync(TwoFactorProviderType type, string token, string email,
string masterPasswordHash)
{
if(type == TwoFactorProviderType.Email || type == TwoFactorProviderType.Authenticator)
{
token = token.Trim().Replace(" ", "");
}
var request = new TokenRequest
{
Email = email.Trim().ToLower(),
MasterPasswordHash = masterPasswordHash,
Token = token.Trim().Replace(" ", ""),
Provider = 0 // Authenticator app (only 1 provider for now, so hard coded)
Token = token,
Provider = type,
Remember = false
};
var response = await ApiService.Instance.PostTokenAsync(request);