2019-04-10 16:49:24 +02:00
using System ;
2019-04-10 21:03:09 +02:00
using System.Collections.Generic ;
2023-06-01 09:35:35 +02:00
using System.Linq ;
2019-04-10 16:49:24 +02:00
using System.Net ;
using System.Net.Http ;
2019-04-10 21:03:09 +02:00
using System.Text ;
2023-06-27 23:49:38 +02:00
using System.Threading ;
2019-04-10 16:49:24 +02:00
using System.Threading.Tasks ;
using Bit.Core.Abstractions ;
2022-02-08 17:43:40 +01:00
using Bit.Core.Enums ;
2019-04-10 16:49:24 +02:00
using Bit.Core.Exceptions ;
2023-11-07 13:15:32 +01:00
using Bit.Core.Models.Data ;
2019-04-10 21:03:09 +02:00
using Bit.Core.Models.Request ;
2019-04-10 16:49:24 +02:00
using Bit.Core.Models.Response ;
2023-03-07 23:16:28 +01:00
using Bit.Core.Utilities ;
2019-04-10 21:03:09 +02:00
using Newtonsoft.Json ;
2019-04-10 16:49:24 +02:00
using Newtonsoft.Json.Linq ;
2019-04-10 21:35:23 +02:00
using Newtonsoft.Json.Serialization ;
2024-02-08 20:05:26 +01:00
using DeviceType = Bit . Core . Enums . DeviceType ;
2019-04-10 16:49:24 +02:00
namespace Bit.Core.Services
{
2019-04-12 05:57:05 +02:00
public class ApiService : IApiService
2019-04-10 16:49:24 +02:00
{
2019-04-10 21:35:23 +02:00
private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver ( )
} ;
2019-06-27 02:28:23 +02:00
private readonly HttpClient _httpClient = new HttpClient ( ) ;
2019-04-10 16:49:24 +02:00
private readonly ITokenService _tokenService ;
private readonly IPlatformUtilsService _platformUtilsService ;
2022-02-23 18:40:17 +01:00
private readonly Func < Tuple < string , bool , bool > , Task > _logoutCallbackAsync ;
2019-04-10 16:49:24 +02:00
public ApiService (
ITokenService tokenService ,
2019-04-10 21:03:09 +02:00
IPlatformUtilsService platformUtilsService ,
2022-02-23 18:40:17 +01:00
Func < Tuple < string , bool , bool > , Task > logoutCallbackAsync ,
2019-09-04 17:52:32 +02:00
string customUserAgent = null )
2019-04-10 16:49:24 +02:00
{
_tokenService = tokenService ;
_platformUtilsService = platformUtilsService ;
2019-04-10 21:03:09 +02:00
_logoutCallbackAsync = logoutCallbackAsync ;
2019-09-05 03:08:08 +02:00
var device = ( int ) _platformUtilsService . GetDevice ( ) ;
2019-09-04 17:52:32 +02:00
_httpClient . DefaultRequestHeaders . Add ( "Device-Type" , device . ToString ( ) ) ;
2022-02-08 17:43:40 +01:00
_httpClient . DefaultRequestHeaders . Add ( "Bitwarden-Client-Name" , _platformUtilsService . GetClientType ( ) . GetString ( ) ) ;
2023-09-28 15:00:20 +02:00
_httpClient . DefaultRequestHeaders . Add ( "Bitwarden-Client-Version" , VersionHelpers . RemoveSuffix ( _platformUtilsService . GetApplicationVersion ( ) ) ) ;
2020-03-28 14:16:28 +01:00
if ( ! string . IsNullOrWhiteSpace ( customUserAgent ) )
2019-09-04 17:52:32 +02:00
{
_httpClient . DefaultRequestHeaders . UserAgent . ParseAdd ( customUserAgent ) ;
}
2019-04-10 16:49:24 +02:00
}
public bool UrlsSet { get ; private set ; }
public string ApiBaseUrl { get ; set ; }
public string IdentityBaseUrl { get ; set ; }
2019-06-25 22:36:21 +02:00
public string EventsBaseUrl { get ; set ; }
2019-04-10 16:49:24 +02:00
2023-11-07 13:15:32 +01:00
public void SetUrls ( EnvironmentUrlData urls )
2019-04-10 16:49:24 +02:00
{
UrlsSet = true ;
2020-03-28 14:16:28 +01:00
if ( ! string . IsNullOrWhiteSpace ( urls . Base ) )
2019-04-10 16:49:24 +02:00
{
ApiBaseUrl = urls . Base + "/api" ;
IdentityBaseUrl = urls . Base + "/identity" ;
2019-06-25 22:36:21 +02:00
EventsBaseUrl = urls . Base + "/events" ;
2019-04-10 16:49:24 +02:00
return ;
}
2019-06-25 22:36:21 +02:00
ApiBaseUrl = urls . Api ;
IdentityBaseUrl = urls . Identity ;
EventsBaseUrl = urls . Events ;
// Production
2020-03-28 14:16:28 +01:00
if ( string . IsNullOrWhiteSpace ( ApiBaseUrl ) )
2019-04-10 16:49:24 +02:00
{
2019-06-25 22:36:21 +02:00
ApiBaseUrl = "https://api.bitwarden.com" ;
}
2020-03-28 14:16:28 +01:00
if ( string . IsNullOrWhiteSpace ( IdentityBaseUrl ) )
2019-06-25 22:36:21 +02:00
{
IdentityBaseUrl = "https://identity.bitwarden.com" ;
}
2020-03-28 14:16:28 +01:00
if ( string . IsNullOrWhiteSpace ( EventsBaseUrl ) )
2019-06-25 22:36:21 +02:00
{
EventsBaseUrl = "https://events.bitwarden.com" ;
2019-04-10 16:49:24 +02:00
}
}
#region Auth APIs
2021-08-04 21:47:23 +02:00
public async Task < IdentityResponse > PostIdentityTokenAsync ( TokenRequest request )
2019-04-10 16:49:24 +02:00
{
2019-04-10 21:03:09 +02:00
var requestMessage = new HttpRequestMessage
2019-04-10 16:49:24 +02:00
{
2019-11-05 15:14:55 +01:00
Version = new Version ( 1 , 0 ) ,
2019-04-10 16:49:24 +02:00
RequestUri = new Uri ( string . Concat ( IdentityBaseUrl , "/connect/token" ) ) ,
2019-04-10 21:03:09 +02:00
Method = HttpMethod . Post ,
2022-02-08 17:43:40 +01:00
Content = new FormUrlEncodedContent ( request . ToIdentityToken ( _platformUtilsService . GetClientType ( ) . GetString ( ) ) )
2019-04-10 16:49:24 +02:00
} ;
2019-04-10 21:03:09 +02:00
requestMessage . Headers . Add ( "Accept" , "application/json" ) ;
2021-05-28 20:06:42 +02:00
request . AlterIdentityTokenHeaders ( requestMessage . Headers ) ;
2019-04-10 16:49:24 +02:00
2019-04-19 15:11:17 +02:00
HttpResponseMessage response ;
try
{
response = await _httpClient . SendAsync ( requestMessage ) ;
}
2019-11-15 14:55:22 +01:00
catch ( Exception e )
2019-04-19 15:11:17 +02:00
{
2019-11-15 14:55:22 +01:00
throw new ApiException ( HandleWebError ( e ) ) ;
2019-04-19 15:11:17 +02:00
}
2019-04-10 16:49:24 +02:00
JObject responseJObject = null ;
2020-03-28 14:16:28 +01:00
if ( IsJsonResponse ( response ) )
2019-04-10 16:49:24 +02:00
{
var responseJsonString = await response . Content . ReadAsStringAsync ( ) ;
responseJObject = JObject . Parse ( responseJsonString ) ;
}
2021-08-04 21:47:23 +02:00
var identityResponse = new IdentityResponse ( response . StatusCode , responseJObject ) ;
if ( identityResponse . FailedToParse )
2019-04-10 16:49:24 +02:00
{
2021-08-04 21:47:23 +02:00
throw new ApiException ( new ErrorResponse ( responseJObject , response . StatusCode , true ) ) ;
2019-04-10 16:49:24 +02:00
}
2021-08-04 21:47:23 +02:00
return identityResponse ;
2019-04-10 16:49:24 +02:00
}
2019-04-10 21:03:09 +02:00
public async Task RefreshIdentityTokenAsync ( )
{
try
{
await DoRefreshTokenAsync ( ) ;
}
catch
{
throw new ApiException ( ) ;
}
}
#endregion
2019-04-10 21:35:23 +02:00
#region Account APIs
2019-04-16 17:07:44 +02:00
public Task < ProfileResponse > GetProfileAsync ( )
2019-04-10 21:35:23 +02:00
{
2019-04-16 17:07:44 +02:00
return SendAsync < object , ProfileResponse > ( HttpMethod . Get , "/accounts/profile" , null , true , true ) ;
2019-04-10 21:35:23 +02:00
}
2019-04-16 17:07:44 +02:00
public Task < PreloginResponse > PostPreloginAsync ( PreloginRequest request )
2019-04-10 21:35:23 +02:00
{
2019-04-16 17:07:44 +02:00
return SendAsync < PreloginRequest , PreloginResponse > ( HttpMethod . Post , "/accounts/prelogin" ,
2023-09-25 17:28:58 +02:00
request , false , true , sendToIdentity : true ) ;
2019-04-10 21:35:23 +02:00
}
2019-04-16 17:07:44 +02:00
public Task < long > GetAccountRevisionDateAsync ( )
2019-04-10 21:35:23 +02:00
{
2019-04-16 17:07:44 +02:00
return SendAsync < object , long > ( HttpMethod . Get , "/accounts/revision-date" , null , true , true ) ;
2019-04-10 21:35:23 +02:00
}
2019-04-16 17:07:44 +02:00
public Task PostPasswordHintAsync ( PasswordHintRequest request )
2019-04-10 21:35:23 +02:00
{
2019-04-16 17:07:44 +02:00
return SendAsync < PasswordHintRequest , object > ( HttpMethod . Post , "/accounts/password-hint" ,
2019-04-10 21:35:23 +02:00
request , false , false ) ;
}
2020-09-03 18:30:40 +02:00
public Task SetPasswordAsync ( SetPasswordRequest request )
{
return SendAsync < SetPasswordRequest , object > ( HttpMethod . Post , "/accounts/set-password" , request , true ,
false ) ;
}
2019-04-16 17:07:44 +02:00
public Task PostRegisterAsync ( RegisterRequest request )
2019-04-10 21:35:23 +02:00
{
2023-09-25 17:28:58 +02:00
return SendAsync < RegisterRequest , object > ( HttpMethod . Post , "/accounts/register" , request , false , false , sendToIdentity : true ) ;
2019-04-10 21:35:23 +02:00
}
2019-04-16 17:07:44 +02:00
public Task PostAccountKeysAsync ( KeysRequest request )
2019-04-10 21:35:23 +02:00
{
2019-04-16 17:07:44 +02:00
return SendAsync < KeysRequest , object > ( HttpMethod . Post , "/accounts/keys" , request , true , false ) ;
}
2023-04-17 16:35:50 +02:00
public Task < VerifyMasterPasswordResponse > PostAccountVerifyPasswordAsync ( PasswordVerificationRequest request )
2020-09-03 18:30:40 +02:00
{
2023-04-17 16:35:50 +02:00
return SendAsync < PasswordVerificationRequest , VerifyMasterPasswordResponse > ( HttpMethod . Post , "/accounts/verify-password" , request ,
true , true ) ;
2020-09-03 18:30:40 +02:00
}
2021-09-24 20:14:26 +02:00
2021-11-11 02:46:48 +01:00
public Task PostAccountRequestOTP ( )
{
2023-03-07 23:16:28 +01:00
return SendAsync < object , object > ( HttpMethod . Post , "/accounts/request-otp" , null , true , false , null , false ) ;
2021-11-11 02:46:48 +01:00
}
public Task PostAccountVerifyOTPAsync ( VerifyOTPRequest request )
{
return SendAsync < VerifyOTPRequest , object > ( HttpMethod . Post , "/accounts/verify-otp" , request ,
2023-03-07 23:16:28 +01:00
true , false , null , false ) ;
2021-11-11 02:46:48 +01:00
}
2021-09-24 20:14:26 +02:00
public Task PutUpdateTempPasswordAsync ( UpdateTempPasswordRequest request )
{
return SendAsync < UpdateTempPasswordRequest , object > ( HttpMethod . Put , "/accounts/update-temp-password" ,
request , true , false ) ;
}
2021-11-11 02:46:48 +01:00
2023-04-17 16:35:50 +02:00
public Task PostPasswordAsync ( PasswordRequest request )
{
return SendAsync < PasswordRequest , object > ( HttpMethod . Post , "/accounts/password" , request , true , false ) ;
}
2021-11-24 20:09:39 +01:00
public Task DeleteAccountAsync ( DeleteAccountRequest request )
{
return SendAsync < DeleteAccountRequest , object > ( HttpMethod . Delete , "/accounts" , request , true , false ) ;
}
2022-04-26 17:21:17 +02:00
2023-08-17 21:19:35 +02:00
public Task PostConvertToKeyConnectorAsync ( )
2021-11-11 02:46:48 +01:00
{
return SendAsync < object , object > ( HttpMethod . Post , "/accounts/convert-to-key-connector" , null , true , false ) ;
}
2023-08-17 21:19:35 +02:00
public Task PostSetKeyConnectorKeyAsync ( SetKeyConnectorKeyRequest request )
2021-11-11 02:46:48 +01:00
{
return SendAsync < SetKeyConnectorKeyRequest > ( HttpMethod . Post , "/accounts/set-key-connector-key" , request , true ) ;
}
2019-04-16 17:07:44 +02:00
#endregion
#region Folder APIs
public Task < FolderResponse > GetFolderAsync ( string id )
{
return SendAsync < object , FolderResponse > ( HttpMethod . Get , string . Concat ( "/folders/" , id ) ,
null , true , true ) ;
}
public Task < FolderResponse > PostFolderAsync ( FolderRequest request )
{
return SendAsync < FolderRequest , FolderResponse > ( HttpMethod . Post , "/folders" , request , true , true ) ;
}
public async Task < FolderResponse > PutFolderAsync ( string id , FolderRequest request )
{
return await SendAsync < FolderRequest , FolderResponse > ( HttpMethod . Put , string . Concat ( "/folders/" , id ) ,
request , true , true ) ;
}
public Task DeleteFolderAsync ( string id )
{
return SendAsync < object , object > ( HttpMethod . Delete , string . Concat ( "/folders/" , id ) , null , true , false ) ;
}
#endregion
2021-01-25 21:27:38 +01:00
#region Send APIs
public Task < SendResponse > GetSendAsync ( string id ) = >
SendAsync < object , SendResponse > ( HttpMethod . Get , $"/sends/{id}" , null , true , true ) ;
public Task < SendResponse > PostSendAsync ( SendRequest request ) = >
SendAsync < SendRequest , SendResponse > ( HttpMethod . Post , "/sends" , request , true , true ) ;
2021-03-29 16:45:04 +02:00
public Task < SendFileUploadDataResponse > PostFileTypeSendAsync ( SendRequest request ) = >
SendAsync < SendRequest , SendFileUploadDataResponse > ( HttpMethod . Post , "/sends/file/v2" , request , true , true ) ;
public Task PostSendFileAsync ( string sendId , string fileId , MultipartFormDataContent data ) = >
SendAsync < MultipartFormDataContent , object > ( HttpMethod . Post , $"/sends/{sendId}/file/{fileId}" , data , true , false ) ;
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
2021-01-25 21:27:38 +01:00
public Task < SendResponse > PostSendFileAsync ( MultipartFormDataContent data ) = >
SendAsync < MultipartFormDataContent , SendResponse > ( HttpMethod . Post , "/sends/file" , data , true , true ) ;
2021-03-29 16:45:04 +02:00
public Task < SendFileUploadDataResponse > RenewFileUploadUrlAsync ( string sendId , string fileId ) = >
SendAsync < object , SendFileUploadDataResponse > ( HttpMethod . Get , $"/sends/{sendId}/file/{fileId}" , null , true , true ) ;
2021-01-25 21:27:38 +01:00
public Task < SendResponse > PutSendAsync ( string id , SendRequest request ) = >
SendAsync < SendRequest , SendResponse > ( HttpMethod . Put , $"/sends/{id}" , request , true , true ) ;
public Task < SendResponse > PutSendRemovePasswordAsync ( string id ) = >
2021-02-11 01:50:10 +01:00
SendAsync < object , SendResponse > ( HttpMethod . Put , $"/sends/{id}/remove-password" , null , true , true ) ;
2021-01-25 21:27:38 +01:00
public Task DeleteSendAsync ( string id ) = >
SendAsync < object , object > ( HttpMethod . Delete , $"/sends/{id}" , null , true , false ) ;
#endregion
2019-04-16 17:07:44 +02:00
#region Cipher APIs
public Task < CipherResponse > GetCipherAsync ( string id )
{
return SendAsync < object , CipherResponse > ( HttpMethod . Get , string . Concat ( "/ciphers/" , id ) ,
null , true , true ) ;
}
public Task < CipherResponse > PostCipherAsync ( CipherRequest request )
{
return SendAsync < CipherRequest , CipherResponse > ( HttpMethod . Post , "/ciphers" , request , true , true ) ;
}
public Task < CipherResponse > PostCipherCreateAsync ( CipherCreateRequest request )
{
return SendAsync < CipherCreateRequest , CipherResponse > ( HttpMethod . Post , "/ciphers/create" ,
request , true , true ) ;
}
public Task < CipherResponse > PutCipherAsync ( string id , CipherRequest request )
{
return SendAsync < CipherRequest , CipherResponse > ( HttpMethod . Put , string . Concat ( "/ciphers/" , id ) ,
request , true , true ) ;
}
public Task < CipherResponse > PutShareCipherAsync ( string id , CipherShareRequest request )
{
return SendAsync < CipherShareRequest , CipherResponse > ( HttpMethod . Put ,
string . Concat ( "/ciphers/" , id , "/share" ) , request , true , true ) ;
}
public Task PutCipherCollectionsAsync ( string id , CipherCollectionsRequest request )
{
return SendAsync < CipherCollectionsRequest , object > ( HttpMethod . Put ,
string . Concat ( "/ciphers/" , id , "/collections" ) , request , true , false ) ;
}
public Task DeleteCipherAsync ( string id )
{
return SendAsync < object , object > ( HttpMethod . Delete , string . Concat ( "/ciphers/" , id ) , null , true , false ) ;
}
2020-05-20 19:35:20 +02:00
public Task PutDeleteCipherAsync ( string id )
{
return SendAsync < object , object > ( HttpMethod . Put , string . Concat ( "/ciphers/" , id , "/delete" ) , null , true , false ) ;
}
2021-01-08 15:53:45 +01:00
public Task < CipherResponse > PutRestoreCipherAsync ( string id )
2020-05-20 19:35:20 +02:00
{
2021-01-08 15:53:45 +01:00
return SendAsync < object , CipherResponse > ( HttpMethod . Put , string . Concat ( "/ciphers/" , id , "/restore" ) , null , true , true ) ;
2020-05-20 19:35:20 +02:00
}
2024-04-12 20:52:39 +02:00
public Task < bool > HasUnassignedCiphersAsync ( )
{
return SendAsync < object , bool > ( HttpMethod . Get , "/ciphers/has-unassigned-ciphers" , null , true , true ) ;
}
2019-04-16 17:07:44 +02:00
#endregion
#region Attachments APIs
2021-03-31 01:42:43 +02:00
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
public Task < CipherResponse > PostCipherAttachmentLegacyAsync ( string id , MultipartFormDataContent data )
2019-04-16 23:21:04 +02:00
{
return SendAsync < MultipartFormDataContent , CipherResponse > ( HttpMethod . Post ,
string . Concat ( "/ciphers/" , id , "/attachment" ) , data , true , true ) ;
}
2021-03-31 01:42:43 +02:00
public Task < AttachmentUploadDataResponse > PostCipherAttachmentAsync ( string id , AttachmentRequest request )
{
return SendAsync < AttachmentRequest , AttachmentUploadDataResponse > ( HttpMethod . Post ,
$"/ciphers/{id}/attachment/v2" , request , true , true ) ;
}
public Task < AttachmentResponse > GetAttachmentData ( string cipherId , string attachmentId ) = >
SendAsync < AttachmentResponse > ( HttpMethod . Get , $"/ciphers/{cipherId}/attachment/{attachmentId}" , true ) ;
2019-04-16 17:07:44 +02:00
public Task DeleteCipherAttachmentAsync ( string id , string attachmentId )
{
2021-03-31 01:42:43 +02:00
return SendAsync ( HttpMethod . Delete ,
string . Concat ( "/ciphers/" , id , "/attachment/" , attachmentId ) , true ) ;
2019-04-16 17:07:44 +02:00
}
2019-04-16 23:21:04 +02:00
public Task PostShareCipherAttachmentAsync ( string id , string attachmentId , MultipartFormDataContent data ,
string organizationId )
{
return SendAsync < MultipartFormDataContent , object > ( HttpMethod . Post ,
string . Concat ( "/ciphers/" , id , "/attachment/" , attachmentId , "/share?organizationId=" , organizationId ) ,
data , true , false ) ;
}
2021-03-31 01:42:43 +02:00
public Task < AttachmentUploadDataResponse > RenewAttachmentUploadUrlAsync ( string cipherId , string attachmentId ) = >
2021-04-12 16:45:17 +02:00
SendAsync < AttachmentUploadDataResponse > ( HttpMethod . Get , $"/ciphers/{cipherId}/attachment/{attachmentId}/renew" , true ) ;
2021-03-31 01:42:43 +02:00
public Task PostAttachmentFileAsync ( string cipherId , string attachmentId , MultipartFormDataContent data ) = >
SendAsync ( HttpMethod . Post ,
$"/ciphers/{cipherId}/attachment/{attachmentId}" , data , true ) ;
2019-04-16 17:07:44 +02:00
#endregion
#region Sync APIs
2019-04-17 18:12:43 +02:00
public Task < SyncResponse > GetSyncAsync ( )
2019-04-16 17:07:44 +02:00
{
return SendAsync < object , SyncResponse > ( HttpMethod . Get , "/sync" , null , true , true ) ;
2019-04-10 21:35:23 +02:00
}
2019-04-10 21:03:09 +02:00
2019-04-10 21:35:23 +02:00
#endregion
2019-04-10 21:03:09 +02:00
2019-05-27 16:28:38 +02:00
#region Two Factor APIs
public Task PostTwoFactorEmailAsync ( TwoFactorEmailRequest request )
{
return SendAsync < TwoFactorEmailRequest , object > (
HttpMethod . Post , "/two-factor/send-email-login" , request , false , false ) ;
}
#endregion
2019-05-28 18:01:55 +02:00
#region Device APIs
2023-08-17 21:19:35 +02:00
public Task < bool > GetKnownDeviceAsync ( string email , string deviceIdentifier )
{
return SendAsync < object , bool > ( HttpMethod . Get , "/devices/knowndevice" , null , false , true , ( message ) = >
{
message . Headers . Add ( "X-Device-Identifier" , deviceIdentifier ) ;
message . Headers . Add ( "X-Request-Email" , CoreHelpers . Base64UrlEncode ( Encoding . UTF8 . GetBytes ( email ) ) ) ;
} ) ;
}
2019-05-28 18:01:55 +02:00
public Task PutDeviceTokenAsync ( string identifier , DeviceTokenRequest request )
{
return SendAsync < DeviceTokenRequest , object > (
2019-05-28 22:20:24 +02:00
HttpMethod . Put , $"/devices/identifier/{identifier}/token" , request , true , false ) ;
2019-05-28 18:01:55 +02:00
}
2023-08-17 21:19:35 +02:00
public Task < bool > GetDevicesExistenceByTypes ( DeviceType [ ] deviceTypes )
{
return SendAsync < DeviceType [ ] , bool > (
HttpMethod . Post , "/devices/exist-by-types" , deviceTypes , true , true ) ;
}
public Task < DeviceResponse > GetDeviceByIdentifierAsync ( string deviceIdentifier )
{
return SendAsync < object , DeviceResponse > ( HttpMethod . Get , $"/devices/identifier/{deviceIdentifier}" , null , true , true ) ;
}
public Task < DeviceResponse > UpdateTrustedDeviceKeysAsync ( string deviceIdentifier , TrustedDeviceKeysRequest trustedDeviceKeysRequest )
{
return SendAsync < TrustedDeviceKeysRequest , DeviceResponse > ( HttpMethod . Put , $"/devices/{deviceIdentifier}/keys" , trustedDeviceKeysRequest , true , true ) ;
}
2019-05-28 18:01:55 +02:00
#endregion
2019-06-25 22:36:21 +02:00
#region Event APIs
2019-07-11 15:30:25 +02:00
public async Task PostEventsCollectAsync ( IEnumerable < EventRequest > request )
2019-06-25 22:36:21 +02:00
{
2020-03-28 14:16:28 +01:00
using ( var requestMessage = new HttpRequestMessage ( ) )
2019-06-25 22:36:21 +02:00
{
2019-11-05 15:14:55 +01:00
requestMessage . Version = new Version ( 1 , 0 ) ;
2019-06-25 22:36:21 +02:00
requestMessage . Method = HttpMethod . Post ;
requestMessage . RequestUri = new Uri ( string . Concat ( EventsBaseUrl , "/collect" ) ) ;
var authHeader = await GetActiveBearerTokenAsync ( ) ;
requestMessage . Headers . Add ( "Authorization" , string . Concat ( "Bearer " , authHeader ) ) ;
requestMessage . Content = new StringContent ( JsonConvert . SerializeObject ( request , _jsonSettings ) ,
Encoding . UTF8 , "application/json" ) ;
HttpResponseMessage response ;
try
{
response = await _httpClient . SendAsync ( requestMessage ) ;
}
2019-11-15 14:55:22 +01:00
catch ( Exception e )
2019-06-25 22:36:21 +02:00
{
2019-11-15 14:55:22 +01:00
throw new ApiException ( HandleWebError ( e ) ) ;
2019-06-25 22:36:21 +02:00
}
2020-03-28 14:16:28 +01:00
if ( ! response . IsSuccessStatusCode )
2019-06-25 22:36:21 +02:00
{
2021-01-25 21:27:38 +01:00
var error = await HandleErrorAsync ( response , false , false ) ;
2019-06-25 22:36:21 +02:00
throw new ApiException ( error ) ;
}
}
}
#endregion
2019-04-17 23:10:21 +02:00
#region HIBP APIs
public Task < List < BreachAccountResponse > > GetHibpBreachAsync ( string username )
{
return SendAsync < object , List < BreachAccountResponse > > ( HttpMethod . Get ,
string . Concat ( "/hibp/breach?username=" , username ) , null , true , true ) ;
}
2021-09-08 19:43:24 +02:00
#endregion
2022-04-26 17:21:17 +02:00
2021-09-08 19:43:24 +02:00
#region Organizations APIs
2022-04-26 17:21:17 +02:00
2021-09-08 19:43:24 +02:00
public Task < OrganizationKeysResponse > GetOrganizationKeysAsync ( string id )
{
return SendAsync < object , OrganizationKeysResponse > ( HttpMethod . Get , $"/organizations/{id}/keys" , null , true , true ) ;
}
2021-09-15 19:27:27 +02:00
public Task < OrganizationAutoEnrollStatusResponse > GetOrganizationAutoEnrollStatusAsync ( string identifier )
{
return SendAsync < object , OrganizationAutoEnrollStatusResponse > ( HttpMethod . Get ,
$"/organizations/{identifier}/auto-enroll-status" , null , true , true ) ;
}
2021-11-11 02:46:48 +01:00
2023-08-17 21:19:35 +02:00
public Task PostLeaveOrganizationAsync ( string id )
2021-11-11 02:46:48 +01:00
{
return SendAsync < object , object > ( HttpMethod . Post , $"/organizations/{id}/leave" , null , true , false ) ;
}
2023-02-20 15:49:20 +01:00
public Task < OrganizationDomainSsoDetailsResponse > GetOrgDomainSsoDetailsAsync ( string userEmail )
{
return SendAsync < OrganizationDomainSsoDetailsRequest , OrganizationDomainSsoDetailsResponse > ( HttpMethod . Post , $"/organizations/domain/sso/details" , new OrganizationDomainSsoDetailsRequest { Email = userEmail } , false , true ) ;
}
2021-09-08 19:43:24 +02:00
#endregion
2021-11-11 02:46:48 +01:00
2021-09-08 19:43:24 +02:00
#region Organization User APIs
public Task PutOrganizationUserResetPasswordEnrollmentAsync ( string orgId , string userId ,
OrganizationUserResetPasswordEnrollmentRequest request )
{
return SendAsync < OrganizationUserResetPasswordEnrollmentRequest , object > ( HttpMethod . Put ,
$"/organizations/{orgId}/users/{userId}/reset-password-enrollment" , request , true , false ) ;
}
2021-11-11 02:46:48 +01:00
#endregion
#region Key Connector
2023-08-17 21:19:35 +02:00
public async Task < KeyConnectorUserKeyResponse > GetMasterKeyFromKeyConnectorAsync ( string keyConnectorUrl )
2021-11-11 02:46:48 +01:00
{
using ( var requestMessage = new HttpRequestMessage ( ) )
{
var authHeader = await GetActiveBearerTokenAsync ( ) ;
requestMessage . Version = new Version ( 1 , 0 ) ;
requestMessage . Method = HttpMethod . Get ;
requestMessage . RequestUri = new Uri ( string . Concat ( keyConnectorUrl , "/user-keys" ) ) ;
requestMessage . Headers . Add ( "Authorization" , string . Concat ( "Bearer " , authHeader ) ) ;
HttpResponseMessage response ;
try
{
response = await _httpClient . SendAsync ( requestMessage ) ;
}
catch ( Exception e )
{
throw new ApiException ( HandleWebError ( e ) ) ;
}
if ( ! response . IsSuccessStatusCode )
{
var error = await HandleErrorAsync ( response , false , true ) ;
throw new ApiException ( error ) ;
}
var responseJsonString = await response . Content . ReadAsStringAsync ( ) ;
return JsonConvert . DeserializeObject < KeyConnectorUserKeyResponse > ( responseJsonString ) ;
}
}
2023-08-17 21:19:35 +02:00
public async Task PostMasterKeyToKeyConnectorAsync ( string keyConnectorUrl , KeyConnectorUserKeyRequest request )
2021-11-11 02:46:48 +01:00
{
using ( var requestMessage = new HttpRequestMessage ( ) )
{
var authHeader = await GetActiveBearerTokenAsync ( ) ;
requestMessage . Version = new Version ( 1 , 0 ) ;
requestMessage . Method = HttpMethod . Post ;
requestMessage . RequestUri = new Uri ( string . Concat ( keyConnectorUrl , "/user-keys" ) ) ;
requestMessage . Headers . Add ( "Authorization" , string . Concat ( "Bearer " , authHeader ) ) ;
requestMessage . Content = new StringContent ( JsonConvert . SerializeObject ( request , _jsonSettings ) ,
Encoding . UTF8 , "application/json" ) ;
HttpResponseMessage response ;
try
{
response = await _httpClient . SendAsync ( requestMessage ) ;
}
catch ( Exception e )
{
throw new ApiException ( HandleWebError ( e ) ) ;
}
if ( ! response . IsSuccessStatusCode )
{
var error = await HandleErrorAsync ( response , false , true ) ;
throw new ApiException ( error ) ;
}
}
}
2019-04-17 23:10:21 +02:00
#endregion
2022-09-26 19:27:57 +02:00
#region PasswordlessLogin
2022-11-15 18:36:21 +01:00
public async Task < List < PasswordlessLoginResponse > > GetAuthRequestAsync ( )
{
var response = await SendAsync < object , PasswordlessLoginsResponse > ( HttpMethod . Get , $"/auth-requests/" , null , true , true ) ;
return response . Data ;
}
2022-09-26 19:27:57 +02:00
public Task < PasswordlessLoginResponse > GetAuthRequestAsync ( string id )
{
return SendAsync < object , PasswordlessLoginResponse > ( HttpMethod . Get , $"/auth-requests/{id}" , null , true , true ) ;
}
2022-11-09 17:25:48 +01:00
public Task < PasswordlessLoginResponse > GetAuthResponseAsync ( string id , string accessCode )
{
return SendAsync < object , PasswordlessLoginResponse > ( HttpMethod . Get , $"/auth-requests/{id}/response?code={accessCode}" , null , false , true ) ;
}
2023-08-17 21:19:35 +02:00
public Task < PasswordlessLoginResponse > PostCreateRequestAsync ( PasswordlessCreateLoginRequest passwordlessCreateLoginRequest , AuthRequestType authRequestType )
2022-11-09 17:25:48 +01:00
{
2024-01-02 14:10:37 +01:00
return SendAsync < object , PasswordlessLoginResponse > ( HttpMethod . Post , authRequestType = = AuthRequestType . AdminApproval ? "/auth-requests/admin-request" : "/auth-requests" , passwordlessCreateLoginRequest , authRequestType = = AuthRequestType . AdminApproval , true ,
( message ) = > message . Headers . Add ( "Device-Identifier" , passwordlessCreateLoginRequest . DeviceIdentifier ) ) ;
2022-11-09 17:25:48 +01:00
}
2022-09-26 19:27:57 +02:00
public Task < PasswordlessLoginResponse > PutAuthRequestAsync ( string id , string encKey , string encMasterPasswordHash , string deviceIdentifier , bool requestApproved )
{
var request = new PasswordlessLoginRequest ( encKey , encMasterPasswordHash , deviceIdentifier , requestApproved ) ;
return SendAsync < object , PasswordlessLoginResponse > ( HttpMethod . Put , $"/auth-requests/{id}" , request , true , true ) ;
}
#endregion
2023-05-19 13:42:41 +02:00
#region Configs
public async Task < ConfigResponse > GetConfigsAsync ( )
{
var accessToken = await _tokenService . GetTokenAsync ( ) ;
return await SendAsync < object , ConfigResponse > ( HttpMethod . Get , "/config/" , null , ! string . IsNullOrEmpty ( accessToken ) , true ) ;
}
#endregion
2019-04-10 21:03:09 +02:00
#region Helpers
public async Task < string > GetActiveBearerTokenAsync ( )
{
var accessToken = await _tokenService . GetTokenAsync ( ) ;
2020-03-28 14:16:28 +01:00
if ( _tokenService . TokenNeedsRefresh ( ) )
2019-04-10 21:03:09 +02:00
{
var tokenResponse = await DoRefreshTokenAsync ( ) ;
accessToken = tokenResponse . AccessToken ;
}
return accessToken ;
}
2023-08-17 21:19:35 +02:00
public async Task < SsoPrevalidateResponse > PreValidateSsoAsync ( string identifier )
2020-09-03 18:30:40 +02:00
{
2024-01-09 22:32:42 +01:00
var path = "/sso/prevalidate?domainHint=" + WebUtility . UrlEncode ( identifier ) ;
2020-09-03 18:30:40 +02:00
using ( var requestMessage = new HttpRequestMessage ( ) )
{
requestMessage . Version = new Version ( 1 , 0 ) ;
requestMessage . Method = HttpMethod . Get ;
requestMessage . RequestUri = new Uri ( string . Concat ( IdentityBaseUrl , path ) ) ;
requestMessage . Headers . Add ( "Accept" , "application/json" ) ;
2022-04-26 17:21:17 +02:00
2020-09-03 18:30:40 +02:00
HttpResponseMessage response ;
try
{
response = await _httpClient . SendAsync ( requestMessage ) ;
}
catch ( Exception e )
{
throw new ApiException ( HandleWebError ( e ) ) ;
}
if ( ! response . IsSuccessStatusCode )
{
2021-01-25 21:27:38 +01:00
var error = await HandleErrorAsync ( response , false , true ) ;
2020-09-03 18:30:40 +02:00
throw new ApiException ( error ) ;
}
2022-06-27 21:53:15 +02:00
var responseJsonString = await response . Content . ReadAsStringAsync ( ) ;
return JsonConvert . DeserializeObject < SsoPrevalidateResponse > ( responseJsonString ) ;
2020-09-03 18:30:40 +02:00
}
}
2021-03-31 01:42:43 +02:00
public Task SendAsync ( HttpMethod method , string path , bool authed ) = >
SendAsync < object , object > ( method , path , null , authed , false ) ;
public Task SendAsync < TRequest > ( HttpMethod method , string path , TRequest body , bool authed ) = >
SendAsync < TRequest , object > ( method , path , body , authed , false ) ;
public Task < TResponse > SendAsync < TResponse > ( HttpMethod method , string path , bool authed ) = >
SendAsync < object , TResponse > ( method , path , null , authed , true ) ;
2019-04-10 21:03:09 +02:00
public async Task < TResponse > SendAsync < TRequest , TResponse > ( HttpMethod method , string path , TRequest body ,
2023-09-25 17:28:58 +02:00
bool authed , bool hasResponse , Action < HttpRequestMessage > alterRequest = null , bool logoutOnUnauthorized = true , bool sendToIdentity = false )
2019-04-10 21:03:09 +02:00
{
2020-03-28 14:16:28 +01:00
using ( var requestMessage = new HttpRequestMessage ( ) )
2019-04-10 21:03:09 +02:00
{
2023-09-25 17:28:58 +02:00
var baseUrl = sendToIdentity ? IdentityBaseUrl : ApiBaseUrl ;
2019-11-05 15:14:55 +01:00
requestMessage . Version = new Version ( 1 , 0 ) ;
2019-04-16 23:21:04 +02:00
requestMessage . Method = method ;
2022-07-11 19:02:11 +02:00
2023-09-25 17:28:58 +02:00
if ( ! Uri . IsWellFormedUriString ( baseUrl , UriKind . Absolute ) )
2022-07-11 19:02:11 +02:00
{
throw new ApiException ( new ErrorResponse
{
StatusCode = HttpStatusCode . BadGateway ,
//Note: This message is hardcoded until AppResources.resx gets moved into Core.csproj
Message = "One or more URLs saved in the Settings are incorrect. Please revise it and try to log in again."
} ) ;
}
2023-09-25 17:28:58 +02:00
requestMessage . RequestUri = new Uri ( string . Concat ( baseUrl , path ) ) ;
2022-07-11 19:02:11 +02:00
2020-03-28 14:16:28 +01:00
if ( body ! = null )
2019-04-10 21:03:09 +02:00
{
2019-04-16 23:21:04 +02:00
var bodyType = body . GetType ( ) ;
2020-03-28 14:16:28 +01:00
if ( bodyType = = typeof ( string ) )
2019-04-16 23:21:04 +02:00
{
requestMessage . Content = new StringContent ( ( object ) bodyType as string , Encoding . UTF8 ,
"application/x-www-form-urlencoded; charset=utf-8" ) ;
}
2020-03-28 14:16:28 +01:00
else if ( bodyType = = typeof ( MultipartFormDataContent ) )
2019-04-16 23:21:04 +02:00
{
requestMessage . Content = body as MultipartFormDataContent ;
}
else
{
requestMessage . Content = new StringContent ( JsonConvert . SerializeObject ( body , _jsonSettings ) ,
Encoding . UTF8 , "application/json" ) ;
}
2019-04-10 21:03:09 +02:00
}
2019-04-16 23:21:04 +02:00
2020-03-28 14:16:28 +01:00
if ( authed )
2019-04-10 21:03:09 +02:00
{
2019-04-16 23:21:04 +02:00
var authHeader = await GetActiveBearerTokenAsync ( ) ;
requestMessage . Headers . Add ( "Authorization" , string . Concat ( "Bearer " , authHeader ) ) ;
2019-04-10 21:03:09 +02:00
}
2020-03-28 14:16:28 +01:00
if ( hasResponse )
2019-04-10 21:03:09 +02:00
{
2019-04-16 23:21:04 +02:00
requestMessage . Headers . Add ( "Accept" , "application/json" ) ;
2019-04-10 21:03:09 +02:00
}
2023-03-07 23:16:28 +01:00
alterRequest ? . Invoke ( requestMessage ) ;
2019-04-10 21:03:09 +02:00
2019-04-19 15:11:17 +02:00
HttpResponseMessage response ;
try
{
response = await _httpClient . SendAsync ( requestMessage ) ;
}
2019-11-15 14:55:22 +01:00
catch ( Exception e )
2019-04-19 15:11:17 +02:00
{
2019-11-15 14:55:22 +01:00
throw new ApiException ( HandleWebError ( e ) ) ;
2019-04-19 15:11:17 +02:00
}
2020-03-28 14:16:28 +01:00
if ( hasResponse & & response . IsSuccessStatusCode )
2019-04-16 23:21:04 +02:00
{
var responseJsonString = await response . Content . ReadAsStringAsync ( ) ;
return JsonConvert . DeserializeObject < TResponse > ( responseJsonString ) ;
}
2020-03-28 14:16:28 +01:00
else if ( ! response . IsSuccessStatusCode )
2019-04-16 23:21:04 +02:00
{
2022-01-24 17:25:46 +01:00
var error = await HandleErrorAsync ( response , false , authed , logoutOnUnauthorized ) ;
2019-04-16 23:21:04 +02:00
throw new ApiException ( error ) ;
}
return ( TResponse ) ( object ) null ;
2019-04-10 21:03:09 +02:00
}
}
public async Task < IdentityTokenResponse > DoRefreshTokenAsync ( )
{
var refreshToken = await _tokenService . GetRefreshTokenAsync ( ) ;
2020-03-28 14:16:28 +01:00
if ( string . IsNullOrWhiteSpace ( refreshToken ) )
2019-04-10 21:03:09 +02:00
{
throw new ApiException ( ) ;
}
var decodedToken = _tokenService . DecodeToken ( ) ;
var requestMessage = new HttpRequestMessage
{
2019-11-05 15:14:55 +01:00
Version = new Version ( 1 , 0 ) ,
2019-04-10 21:03:09 +02:00
RequestUri = new Uri ( string . Concat ( IdentityBaseUrl , "/connect/token" ) ) ,
Method = HttpMethod . Post ,
Content = new FormUrlEncodedContent ( new Dictionary < string , string >
{
["grant_type"] = "refresh_token" ,
["client_id"] = decodedToken . GetValue ( "client_id" ) ? . Value < string > ( ) ,
["refresh_token"] = refreshToken
} )
} ;
requestMessage . Headers . Add ( "Accept" , "application/json" ) ;
2019-04-19 15:11:17 +02:00
HttpResponseMessage response ;
try
{
response = await _httpClient . SendAsync ( requestMessage ) ;
}
2019-11-15 14:55:22 +01:00
catch ( Exception e )
2019-04-19 15:11:17 +02:00
{
2019-11-15 14:55:22 +01:00
throw new ApiException ( HandleWebError ( e ) ) ;
2019-04-19 15:11:17 +02:00
}
2020-03-28 14:16:28 +01:00
if ( response . IsSuccessStatusCode )
2019-04-10 21:03:09 +02:00
{
var responseJsonString = await response . Content . ReadAsStringAsync ( ) ;
var tokenResponse = JsonConvert . DeserializeObject < IdentityTokenResponse > ( responseJsonString ) ;
await _tokenService . SetTokensAsync ( tokenResponse . AccessToken , tokenResponse . RefreshToken ) ;
return tokenResponse ;
}
else
{
2021-01-25 21:27:38 +01:00
var error = await HandleErrorAsync ( response , true , true ) ;
2019-04-10 21:03:09 +02:00
throw new ApiException ( error ) ;
}
}
2023-06-27 23:49:38 +02:00
public async Task < HttpResponseMessage > SendAsync ( HttpRequestMessage requestMessage , CancellationToken cancellationToken = default )
2022-08-26 20:32:02 +02:00
{
2023-06-27 23:49:38 +02:00
HttpResponseMessage response ;
try
2022-08-26 20:32:02 +02:00
{
2023-06-27 23:49:38 +02:00
response = await _httpClient . SendAsync ( requestMessage , cancellationToken ) ;
2022-08-26 20:32:02 +02:00
}
2023-06-27 23:49:38 +02:00
catch ( Exception e )
2023-01-26 14:53:48 +01:00
{
2023-06-27 23:49:38 +02:00
throw new ApiException ( HandleWebError ( e ) ) ;
2023-01-26 14:53:48 +01:00
}
2023-06-27 23:49:38 +02:00
if ( ! response . IsSuccessStatusCode )
2023-01-26 14:53:48 +01:00
{
2023-06-27 23:49:38 +02:00
throw new ApiException ( new ErrorResponse
2023-01-26 14:53:48 +01:00
{
2023-06-27 23:49:38 +02:00
StatusCode = response . StatusCode ,
Message = $"{requestMessage.RequestUri} error: {(int)response.StatusCode} {response.ReasonPhrase}."
} ) ;
2023-01-26 14:53:48 +01:00
}
2023-06-27 23:49:38 +02:00
return response ;
2023-01-26 14:53:48 +01:00
}
2023-06-27 23:49:38 +02:00
public async Task < string > GetFastmailAccountIdAsync ( string apiKey )
2023-01-26 14:53:48 +01:00
{
using ( var httpclient = new HttpClient ( ) )
{
HttpResponseMessage response ;
try
{
httpclient . DefaultRequestHeaders . Add ( "Authorization" , $"Bearer {apiKey}" ) ;
httpclient . DefaultRequestHeaders . Add ( "Accept" , "application/json" ) ;
response = await httpclient . GetAsync ( new Uri ( "https://api.fastmail.com/jmap/session" ) ) ;
}
catch ( Exception e )
{
throw new ApiException ( HandleWebError ( e ) ) ;
}
if ( ! response . IsSuccessStatusCode )
{
throw new ApiException ( new ErrorResponse
{
StatusCode = response . StatusCode ,
Message = $"Fastmail error: {(int)response.StatusCode} {response.ReasonPhrase}."
} ) ;
}
var result = JObject . Parse ( await response . Content . ReadAsStringAsync ( ) ) ;
2023-06-27 23:49:38 +02:00
return result [ "primaryAccounts" ] ? [ "https://www.fastmail.com/dev/maskedemail" ] ? . ToString ( ) ;
2023-01-26 14:53:48 +01:00
}
}
2019-11-15 14:55:22 +01:00
private ErrorResponse HandleWebError ( Exception e )
2019-04-19 15:11:17 +02:00
{
return new ErrorResponse
{
StatusCode = HttpStatusCode . BadGateway ,
2019-11-15 14:55:22 +01:00
Message = "Exception message: " + e . Message
2019-04-19 15:11:17 +02:00
} ;
}
2022-01-24 17:25:46 +01:00
private async Task < ErrorResponse > HandleErrorAsync ( HttpResponseMessage response , bool tokenError ,
bool authed , bool logoutOnUnauthorized = true )
2019-04-10 21:03:09 +02:00
{
2022-01-24 17:25:46 +01:00
if ( authed
& &
(
( logoutOnUnauthorized & & response . StatusCode = = HttpStatusCode . Unauthorized )
| |
response . StatusCode = = HttpStatusCode . Forbidden
) )
2019-04-10 21:03:09 +02:00
{
2022-02-23 18:40:17 +01:00
await _logoutCallbackAsync ( new Tuple < string , bool , bool > ( null , false , true ) ) ;
2019-04-10 21:03:09 +02:00
return null ;
}
2020-09-03 18:30:40 +02:00
try
2019-04-10 21:03:09 +02:00
{
2020-09-03 18:30:40 +02:00
JObject responseJObject = null ;
if ( IsJsonResponse ( response ) )
{
var responseJsonString = await response . Content . ReadAsStringAsync ( ) ;
responseJObject = JObject . Parse ( responseJsonString ) ;
}
2022-10-31 18:40:26 +01:00
if ( authed & & tokenError
& &
response . StatusCode = = HttpStatusCode . BadRequest
& &
responseJObject ? [ "error" ] ? . ToString ( ) = = "invalid_grant" )
{
await _logoutCallbackAsync ( new Tuple < string , bool , bool > ( null , false , true ) ) ;
return null ;
}
2020-09-03 18:30:40 +02:00
return new ErrorResponse ( responseJObject , response . StatusCode , tokenError ) ;
}
catch
{
return null ;
2019-04-10 21:03:09 +02:00
}
}
private bool IsJsonResponse ( HttpResponseMessage response )
{
2023-06-01 09:35:35 +02:00
if ( response . Content ? . Headers is null )
{
return false ;
}
if ( response . Content . Headers . ContentType ? . MediaType = = "application/json" )
{
return true ;
}
return response . Content . Headers . TryGetValues ( "Content-Type" , out var vals )
& &
vals ? . Any ( v = > v . Contains ( "application/json" ) ) is true ;
2019-04-10 21:03:09 +02:00
}
2019-04-10 16:49:24 +02:00
#endregion
}
}