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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
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.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; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
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.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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user