using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Bit.App.Models.Api; using Newtonsoft.Json; using Plugin.Connectivity.Abstractions; using Bit.App.Abstractions; using System.Net; using XLabs.Ioc; using Newtonsoft.Json.Linq; namespace Bit.App.Repositories { public abstract class BaseApiRepository { public BaseApiRepository( IConnectivity connectivity, IHttpService httpService, ITokenService tokenService) { Connectivity = connectivity; HttpService = httpService; TokenService = tokenService; } protected IConnectivity Connectivity { get; private set; } protected IHttpService HttpService { get; private set; } protected ITokenService TokenService { get; private set; } protected abstract string ApiRoute { get; } protected async Task HandleTokenStateAsync() { return await HandleTokenStateAsync( () => ApiResult.Success(HttpStatusCode.OK), () => HandledWebException(), (r) => HandleErrorAsync(r)); } protected async Task> HandleTokenStateAsync() { return await HandleTokenStateAsync( () => ApiResult.Success(default(T), HttpStatusCode.OK), () => HandledWebException(), (r) => HandleErrorAsync(r)); } private async Task HandleTokenStateAsync(Func success, Func webException, Func> error) { if(!string.IsNullOrWhiteSpace(TokenService.AuthBearer) && string.IsNullOrWhiteSpace(TokenService.Token)) { // Migrate from old auth bearer to new access token var deviceInfoService = Resolver.Resolve(); var appIdService = Resolver.Resolve(); using(var client = HttpService.Client) { var requestMessage = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri(client.BaseAddress, "connect/token"), Content = new FormUrlEncodedContent(new TokenRequest { Email = "abcdefgh", MasterPasswordHash = "abcdefgh", OldAuthBearer = TokenService.AuthBearer, Device = new DeviceRequest(appIdService, deviceInfoService) }.ToIdentityTokenRequest()) }; try { var response = await client.SendAsync(requestMessage).ConfigureAwait(false); if(!response.IsSuccessStatusCode) { if(response.StatusCode == HttpStatusCode.BadRequest) { response.StatusCode = HttpStatusCode.Unauthorized; } return await error.Invoke(response).ConfigureAwait(false); } var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var tokenResponse = JsonConvert.DeserializeObject(responseContent); TokenService.Token = tokenResponse.AccessToken; TokenService.RefreshToken = tokenResponse.RefreshToken; TokenService.AuthBearer = null; } catch { return webException.Invoke(); } } } else if(TokenService.TokenNeedsRefresh && !string.IsNullOrWhiteSpace(TokenService.RefreshToken)) { using(var client = HttpService.Client) { var requestMessage = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri(client.BaseAddress, "connect/token"), Content = new FormUrlEncodedContent(new Dictionary { { "grant_type", "refresh_token" }, { "client_id", "mobile" }, { "refresh_token", TokenService.RefreshToken } }) }; try { var response = await client.SendAsync(requestMessage).ConfigureAwait(false); if(!response.IsSuccessStatusCode) { if(response.StatusCode == HttpStatusCode.BadRequest) { response.StatusCode = HttpStatusCode.Unauthorized; } return await error.Invoke(response).ConfigureAwait(false); } var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var tokenResponse = JsonConvert.DeserializeObject(responseContent); TokenService.Token = tokenResponse.AccessToken; TokenService.RefreshToken = tokenResponse.RefreshToken; } catch { return webException.Invoke(); } } } else if(!string.IsNullOrWhiteSpace(TokenService.AuthBearer)) { TokenService.AuthBearer = null; } return success.Invoke(); } protected ApiResult HandledNotConnected() { return ApiResult.Failed(HttpStatusCode.RequestTimeout, new ApiError { Message = "Not connected to the internet." }); } protected ApiResult HandledNotConnected() { return ApiResult.Failed(HttpStatusCode.RequestTimeout, new ApiError { Message = "Not connected to the internet." }); } protected ApiResult HandledWebException() { return ApiResult.Failed(HttpStatusCode.BadGateway, new ApiError { Message = "There is a problem connecting to the server." }); } protected ApiResult HandledWebException() { return ApiResult.Failed(HttpStatusCode.BadGateway, new ApiError { Message = "There is a problem connecting to the server." }); } protected async Task> HandleErrorAsync(HttpResponseMessage response) { try { var errors = await ParseErrorsAsync(response).ConfigureAwait(false); return ApiResult.Failed(response.StatusCode, errors.ToArray()); } catch { } return ApiResult.Failed(response.StatusCode, new ApiError { Message = "An unknown error has occured." }); } protected async Task HandleErrorAsync(HttpResponseMessage response) { try { var errors = await ParseErrorsAsync(response).ConfigureAwait(false); return ApiResult.Failed(response.StatusCode, errors.ToArray()); } catch { } return ApiResult.Failed(response.StatusCode, new ApiError { Message = "An unknown error has occured." }); } private async Task> ParseErrorsAsync(HttpResponseMessage response) { var errors = new List(); var statusCode = (int)response.StatusCode; if(statusCode >= 400 && statusCode <= 500) { ErrorResponse errorResponseModel = null; var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); if(!string.IsNullOrWhiteSpace(responseContent)) { var errorResponse = JObject.Parse(responseContent); if(errorResponse["ErrorModel"] != null && errorResponse["ErrorModel"]["Message"] != null) { errorResponseModel = errorResponse["ErrorModel"].ToObject(); } else if(errorResponse["Message"] != null) { errorResponseModel = errorResponse.ToObject(); } } if(errorResponseModel != null) { if((errorResponseModel.ValidationErrors?.Count ?? 0) > 0) { foreach(var valError in errorResponseModel.ValidationErrors) { foreach(var errorMessage in valError.Value) { errors.Add(new ApiError { Message = errorMessage }); } } } else { errors.Add(new ApiError { Message = errorResponseModel.Message }); } } } if(errors.Count == 0) { errors.Add(new ApiError { Message = "An unknown error has occured." }); } return errors; } } }