stub out beginnings of apiservice

This commit is contained in:
Kyle Spearrin 2019-04-10 10:49:24 -04:00
parent 0d417b3eee
commit 579a7e0398
10 changed files with 279 additions and 1 deletions

View file

@ -1,5 +1,6 @@
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using Bit.Core.Abstractions;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -8,7 +9,7 @@ using Xamarin.Forms;
namespace Bit.App.Services namespace Bit.App.Services
{ {
public class MobilePlatformUtilsService public class MobilePlatformUtilsService : IPlatformUtilsService
{ {
private static readonly Random _random = new Random(); private static readonly Random _random = new Random();

View file

@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.Core.Abstractions
{
public interface IPlatformUtilsService
{
string IdentityClientId { get; }
Task CopyToClipboardAsync(string text, Dictionary<string, object> options = null);
string GetApplicationVersion();
DeviceType GetDevice();
string GetDeviceString();
bool IsDev();
bool IsSelfHost();
bool IsViewOpen();
void LaunchUri(string uri, Dictionary<string, object> options = null);
int? LockTimeout();
Task<string> ReadFromClipboardAsync(Dictionary<string, object> options = null);
void SaveFile();
Task<bool> ShowDialogAsync(string text, string title = null, string confirmText = null,
string cancelText = null, string type = null);
void ShowToast(string type, string title, string text, Dictionary<string, object> options = null);
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
bool SupportsU2f();
}
}

View file

@ -0,0 +1,14 @@
using Bit.Core.Models.Response;
using System;
namespace Bit.Core.Exceptions
{
public class ApiException : Exception
{
public ApiException(ErrorResponse error)
: base("An API error has occurred.")
{ }
public ErrorResponse Error { get; set; }
}
}

View file

@ -0,0 +1,9 @@
namespace Bit.Core.Models.Domain
{
public class EnvironmentUrls
{
public string Base { get; set; }
public string Api { get; set; }
public string Identity { get; set; }
}
}

View file

@ -0,0 +1,21 @@
using Bit.Core.Abstractions;
using Bit.Core.Enums;
namespace Bit.Core.Models.Request
{
public class DeviceRequest
{
public DeviceRequest(string appId, IPlatformUtilsService platformUtilsService)
{
Type = platformUtilsService.GetDevice();
Name = platformUtilsService.GetDeviceString();
Identifier = appId;
PushToken = null; // TODO?
}
public DeviceType? Type { get; set; }
public string Name { get; set; }
public string Identifier { get; set; }
public string PushToken { get; set; }
}
}

View file

@ -0,0 +1,17 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Text;
namespace Bit.Core.Models.Request
{
public class TokenRequest
{
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
public string Token { get; set; }
public TwoFactorProviderType Provider { get; set; }
public bool Remember { get; set; }
public DeviceRequest Device { get; set; }
}
}

View file

@ -0,0 +1,62 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace Bit.Core.Models.Response
{
public class ErrorResponse
{
public ErrorResponse(JObject response, HttpStatusCode status, bool identityResponse = false)
{
JObject errorModel = null;
if(response != null)
{
var responseErrorModel = response.GetValue("ErrorModel", StringComparison.OrdinalIgnoreCase);
if(responseErrorModel != null && identityResponse)
{
errorModel = responseErrorModel.Value<JObject>(); ;
}
else
{
errorModel = response;
}
}
if(errorModel != null)
{
Message = errorModel.GetValue("Message", StringComparison.OrdinalIgnoreCase)?.Value<string>();
ValidationErrors = errorModel.GetValue("ValidationErrors", StringComparison.OrdinalIgnoreCase)
?.Value<Dictionary<string, List<string>>>();
}
else
{
if((int)status == 429)
{
Message = "Rate limit exceeded. Try again later.";
}
}
StatusCode = status;
}
public string Message { get; set; }
public Dictionary<string, List<string>> ValidationErrors { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string GetSingleMessage()
{
if(ValidationErrors == null)
{
return Message;
}
foreach(var error in ValidationErrors)
{
if(error.Value?.Any() ?? false)
{
return error.Value[0];
}
}
return Message;
}
}
}

View file

@ -0,0 +1,13 @@
namespace Bit.Core.Models.Response
{
public class IdentityTokenResponse
{
public string AccessToken { get; set; }
public string ExpiresIn { get; set; }
public string RefreshToken { get; set; }
public string TokenType { get; set; }
public string PrivateKey { get; set; }
public string Key { get; set; }
public string TwoFactorToken { get; set; }
}
}

View file

@ -0,0 +1,11 @@
using Bit.Core.Enums;
using System.Collections.Generic;
namespace Bit.Core.Models.Response
{
public class IdentityTwoFactorResponse
{
public List<TwoFactorProviderType> TwoFactorProviders { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders2 { get; set; }
}
}

View file

@ -0,0 +1,102 @@
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace Bit.Core.Services
{
public class ApiService
{
private readonly HttpClient _httpClient = new HttpClient();
private readonly ITokenService _tokenService;
private readonly IPlatformUtilsService _platformUtilsService;
private string _deviceType;
private bool _usingBaseUrl = false;
public ApiService(
ITokenService tokenService,
IPlatformUtilsService platformUtilsService)
{
_tokenService = tokenService;
_platformUtilsService = platformUtilsService;
}
public bool UrlsSet { get; private set; }
public string ApiBaseUrl { get; set; }
public string IdentityBaseUrl { get; set; }
public void SetUrls(EnvironmentUrls urls)
{
UrlsSet = true;
if(!string.IsNullOrWhiteSpace(urls.Base))
{
_usingBaseUrl = true;
ApiBaseUrl = urls.Base + "/api";
IdentityBaseUrl = urls.Base + "/identity";
return;
}
if(!string.IsNullOrWhiteSpace(urls.Api) && !string.IsNullOrWhiteSpace(urls.Identity))
{
ApiBaseUrl = urls.Api;
IdentityBaseUrl = urls.Identity;
return;
}
// Local Dev
//ApiBaseUrl = "http://localhost:4000";
//IdentityBaseUrl = "http://localhost:33656";
// Production
ApiBaseUrl = "https://api.bitwarden.com";
IdentityBaseUrl = "https://identity.bitwarden.com";
}
#region Auth APIs
public async Task<Tuple<IdentityTokenResponse, IdentityTwoFactorResponse>> PostIdentityTokenAsync()
{
var request = new HttpRequestMessage
{
RequestUri = new Uri(string.Concat(IdentityBaseUrl, "/connect/token")),
Method = HttpMethod.Post
};
request.Headers.Add("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Device-Type", _deviceType);
var response = await _httpClient.SendAsync(request);
JObject responseJObject = null;
if(response.Headers.Contains("content-type") &&
response.Headers.GetValues("content-type").Any(h => h.Contains("application/json")))
{
var responseJsonString = await response.Content.ReadAsStringAsync();
responseJObject = JObject.Parse(responseJsonString);
}
if(responseJObject != null)
{
if(response.IsSuccessStatusCode)
{
return new Tuple<IdentityTokenResponse, IdentityTwoFactorResponse>(
responseJObject.ToObject<IdentityTokenResponse>(), null);
}
else if(response.StatusCode == HttpStatusCode.BadRequest &&
responseJObject.ContainsKey("TwoFactorProviders2") &&
responseJObject["TwoFactorProviders2"] != null &&
responseJObject["TwoFactorProviders2"].HasValues)
{
return new Tuple<IdentityTokenResponse, IdentityTwoFactorResponse>(
null, responseJObject.ToObject<IdentityTwoFactorResponse>());
}
}
throw new ApiException(new ErrorResponse(responseJObject, response.StatusCode, true));
}
#endregion
}
}