mirror of
https://github.com/bitwarden/directory-connector
synced 2025-12-17 08:43:27 +00:00
added support additional two factor providers during login
This commit is contained in:
@@ -1,19 +1,16 @@
|
|||||||
using Bit.Core.Models;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Con = System.Console;
|
using Con = System.Console;
|
||||||
|
|
||||||
namespace Bit.Console
|
namespace Bit.Console
|
||||||
{
|
{
|
||||||
class Program
|
public class Program
|
||||||
{
|
{
|
||||||
private static bool _usingArgs = false;
|
private static bool _usingArgs = false;
|
||||||
private static bool _exit = false;
|
private static bool _exit = false;
|
||||||
@@ -155,6 +152,7 @@ namespace Bit.Console
|
|||||||
string masterPassword = null;
|
string masterPassword = null;
|
||||||
string token = null;
|
string token = null;
|
||||||
string orgId = null;
|
string orgId = null;
|
||||||
|
var provider = TwoFactorProviderType.Authenticator;
|
||||||
|
|
||||||
if(_usingArgs)
|
if(_usingArgs)
|
||||||
{
|
{
|
||||||
@@ -164,7 +162,8 @@ namespace Bit.Console
|
|||||||
email = parameters["e"];
|
email = parameters["e"];
|
||||||
masterPassword = parameters["p"];
|
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"];
|
token = parameters["t"];
|
||||||
}
|
}
|
||||||
@@ -198,21 +197,81 @@ namespace Bit.Console
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = await AuthService.Instance.LogInTwoFactorAsync(email, masterPassword, token);
|
result = await AuthService.Instance.LogInTwoFactorAsync(provider, token,
|
||||||
|
email, masterPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(token) && result.TwoFactorRequired)
|
if(string.IsNullOrWhiteSpace(token) && result.TwoFactorRequired)
|
||||||
{
|
{
|
||||||
Con.WriteLine();
|
Con.WriteLine();
|
||||||
Con.WriteLine();
|
Con.WriteLine();
|
||||||
Con.WriteLine("Two-step login is enabled on this account. Please enter your verification code.");
|
Con.WriteLine("Two-step login is enabled on this account.");
|
||||||
Con.Write("Verification code: ");
|
if(result.TwoFactorProviders.Count > 1)
|
||||||
token = Con.ReadLine().Trim();
|
{
|
||||||
result = await AuthService.Instance.LogInTwoFactorWithHashAsync(token, email,
|
for(var i = 0; i < result.TwoFactorProviders.Count; i++)
|
||||||
result.MasterPasswordHash);
|
{
|
||||||
|
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;
|
Organization org = null;
|
||||||
if(!string.IsNullOrWhiteSpace(orgId))
|
if(!string.IsNullOrWhiteSpace(orgId))
|
||||||
@@ -252,11 +311,18 @@ namespace Bit.Console
|
|||||||
Con.WriteLine();
|
Con.WriteLine();
|
||||||
Con.WriteLine();
|
Con.WriteLine();
|
||||||
if(result.Success)
|
if(result.Success)
|
||||||
|
{
|
||||||
|
if(!result.TwoFactorRequired)
|
||||||
{
|
{
|
||||||
WriteSuccessLine(string.Format("You have successfully logged in as {0}!",
|
WriteSuccessLine(string.Format("You have successfully logged in as {0}!",
|
||||||
TokenService.Instance.AccessTokenEmail));
|
TokenService.Instance.AccessTokenEmail));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
WriteErrorLine("Unable to log in.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
WriteErrorLine(result.ErrorMessage);
|
WriteErrorLine(result.ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
<Compile Include="Enums\DirectoryType.cs" />
|
<Compile Include="Enums\DirectoryType.cs" />
|
||||||
<Compile Include="Enums\OrganizationUserType.cs" />
|
<Compile Include="Enums\OrganizationUserType.cs" />
|
||||||
<Compile Include="Enums\OrganizationUserStatusType.cs" />
|
<Compile Include="Enums\OrganizationUserStatusType.cs" />
|
||||||
|
<Compile Include="Enums\TwoFactorProviderType.cs" />
|
||||||
<Compile Include="Enums\UserAccountControl.cs" />
|
<Compile Include="Enums\UserAccountControl.cs" />
|
||||||
<Compile Include="Installer.cs" />
|
<Compile Include="Installer.cs" />
|
||||||
<Compile Include="Models\ApiError.cs" />
|
<Compile Include="Models\ApiError.cs" />
|
||||||
@@ -107,6 +108,7 @@
|
|||||||
<Compile Include="Models\TokenRequest.cs" />
|
<Compile Include="Models\TokenRequest.cs" />
|
||||||
<Compile Include="Models\ProfileResponse.cs" />
|
<Compile Include="Models\ProfileResponse.cs" />
|
||||||
<Compile Include="Models\TokenResponse.cs" />
|
<Compile Include="Models\TokenResponse.cs" />
|
||||||
|
<Compile Include="Models\TwoFactorEmailRequest.cs" />
|
||||||
<Compile Include="Services\ApiService.cs" />
|
<Compile Include="Services\ApiService.cs" />
|
||||||
<Compile Include="Services\GSuiteDirectoryService.cs" />
|
<Compile Include="Services\GSuiteDirectoryService.cs" />
|
||||||
<Compile Include="Services\ControllerService.cs" />
|
<Compile Include="Services\ControllerService.cs" />
|
||||||
|
|||||||
12
src/Core/Enums/TwoFactorProviderType.cs
Normal file
12
src/Core/Enums/TwoFactorProviderType.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using Bit.Core.Enums;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -10,7 +11,8 @@ namespace Bit.Core.Models
|
|||||||
{
|
{
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
public string ErrorMessage { 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 string MasterPasswordHash { get; set; }
|
||||||
public List<Organization> Organizations { get; set; }
|
public List<Organization> Organizations { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using Bit.Core.Enums;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
namespace Bit.Core.Models
|
||||||
@@ -8,7 +9,8 @@ namespace Bit.Core.Models
|
|||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string MasterPasswordHash { get; set; }
|
public string MasterPasswordHash { get; set; }
|
||||||
public string Token { 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()
|
public IDictionary<string, string> ToIdentityTokenRequest()
|
||||||
{
|
{
|
||||||
@@ -24,7 +26,8 @@ namespace Bit.Core.Models
|
|||||||
if(Token != null && Provider.HasValue)
|
if(Token != null && Provider.HasValue)
|
||||||
{
|
{
|
||||||
dict.Add("TwoFactorToken", Token);
|
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;
|
return dict;
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
using Newtonsoft.Json;
|
using Bit.Core.Enums;
|
||||||
using System;
|
using Newtonsoft.Json;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
namespace Bit.Core.Models
|
||||||
{
|
{
|
||||||
@@ -17,7 +14,9 @@ namespace Bit.Core.Models
|
|||||||
public string RefreshToken { get; set; }
|
public string RefreshToken { get; set; }
|
||||||
[JsonProperty("token_type")]
|
[JsonProperty("token_type")]
|
||||||
public string TokenType { get; set; }
|
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 PrivateKey { get; set; }
|
||||||
|
public string TwoFactorToken { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/Core/Models/TwoFactorEmailRequest.cs
Normal file
8
src/Core/Models/TwoFactorEmailRequest.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Bit.Core.Models
|
||||||
|
{
|
||||||
|
public class TwoFactorEmailRequest
|
||||||
|
{
|
||||||
|
public string Email { get; set; }
|
||||||
|
public string MasterPasswordHash { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Models;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
@@ -51,11 +52,12 @@ namespace Bit.Core.Services
|
|||||||
if(!response.IsSuccessStatusCode)
|
if(!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
var errorResponse = JObject.Parse(responseContent);
|
var errorResponse = JObject.Parse(responseContent);
|
||||||
if(errorResponse["TwoFactorProviders"] != null)
|
if(errorResponse["TwoFactorProviders2"] != null)
|
||||||
{
|
{
|
||||||
return ApiResult<TokenResponse>.Success(new TokenResponse
|
return ApiResult<TokenResponse>.Success(new TokenResponse
|
||||||
{
|
{
|
||||||
TwoFactorProviders = errorResponse["TwoFactorProviders"].ToObject<List<int>>()
|
TwoFactorProviders2 = errorResponse["TwoFactorProviders2"]
|
||||||
|
.ToObject<Dictionary<TwoFactorProviderType, Dictionary<string, object>>>()
|
||||||
}, response.StatusCode);
|
}, 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()
|
protected ApiResult HandledWebException()
|
||||||
{
|
{
|
||||||
return ApiResult.Failed(HttpStatusCode.BadGateway,
|
return ApiResult.Failed(HttpStatusCode.BadGateway,
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.Success = true;
|
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;
|
result.MasterPasswordHash = request.MasterPasswordHash;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -73,12 +73,13 @@ namespace Bit.Core.Services
|
|||||||
return await ProcessLogInSuccessAsync(response.Result);
|
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 normalizedEmail = email.Trim().ToLower();
|
||||||
var key = Crypto.MakeKeyFromPassword(masterPassword, normalizedEmail);
|
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;
|
key = null;
|
||||||
masterPassword = null;
|
masterPassword = null;
|
||||||
@@ -86,14 +87,21 @@ namespace Bit.Core.Services
|
|||||||
return result;
|
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
|
var request = new TokenRequest
|
||||||
{
|
{
|
||||||
Email = email.Trim().ToLower(),
|
Email = email.Trim().ToLower(),
|
||||||
MasterPasswordHash = masterPasswordHash,
|
MasterPasswordHash = masterPasswordHash,
|
||||||
Token = token.Trim().Replace(" ", ""),
|
Token = token,
|
||||||
Provider = 0 // Authenticator app (only 1 provider for now, so hard coded)
|
Provider = type,
|
||||||
|
Remember = false
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = await ApiService.Instance.PostTokenAsync(request);
|
var response = await ApiService.Instance.PostTokenAsync(request);
|
||||||
|
|||||||
Reference in New Issue
Block a user