Skip to content

Commit

Permalink
fix: correctly handle settings/import link generation
Browse files Browse the repository at this point in the history
Settings' `Import` button now correctly redirects the user by handling
subdomain type (`nested` or `flat`) and by setting correct hash
  • Loading branch information
Ldoppea committed Nov 19, 2021
1 parent f91cb81 commit f9cb62f
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 17 deletions.
6 changes: 3 additions & 3 deletions src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ public void Rate()
_deviceActionService.RateApp();
}

public void Import()
public async void Import()
{
var passwordsURL = _cozyClientService.GetURLForApp("passwords", fragment: "/installation/import");
var passwordsURL = await _cozyClientService.GetURLForApp("passwords", fragment: "/vault?action=import");
_platformUtilsService.LaunchUri(passwordsURL);
}

Expand Down Expand Up @@ -199,7 +199,7 @@ public async Task TwoStepAsync()
AppResources.TwoStepLogin, AppResources.Yes, AppResources.Cancel);
if (confirmed)
{
var twoFAURL = _cozyClientService.GetURLForApp("settings", fragment: "/profile");
var twoFAURL = await _cozyClientService.GetURLForApp("settings", fragment: "/profile");
_platformUtilsService.LaunchUri(twoFAURL);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Abstractions/ICozyClientService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public interface ICozyClientService
Task<LogoutResponse> LogoutAsync();
string GetEmailFromCozyURL(string cozyURL);
Task ConfigureEnvironmentFromCozyURLAsync(string cozyURL);
string GetURLForApp(string appname, string fragment);
Task<string> GetURLForApp(string appname, string fragment);
string GetRegistrationURL(string lang);
bool CheckStateAndSecretInOnboardingCallbackURL();
Task UpdateSynchronizedAtAsync();
}
}
}
1 change: 1 addition & 0 deletions src/Core/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

<ItemGroup>
<PackageReference Include="CsvHelper" Version="27.1.1" />
<PackageReference Include="Flurl" Version="3.0.2" />
<PackageReference Include="LiteDB" Version="5.0.11" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="PCLCrypto" Version="2.0.147" />
Expand Down
58 changes: 46 additions & 12 deletions src/Core/Services/CozyClientService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using Bit.Core.Utilities;

namespace Bit.Core.Services
{
Expand Down Expand Up @@ -206,19 +208,29 @@ public async Task ConfigureEnvironmentFromCozyURLAsync(string userCozyURL)
await _environmentService.SetUrlsAsync(environmentData);
}

public string GetURLForApp(string appName, string fragment = null)
public async Task<string> GetURLForApp(string appName, string fragment = null)
{
var url = GetCozyURL();
var builder = new UriBuilder(url);
var host = builder.Host;
var parts = host.Split('.');
parts[0] = $"{parts[0]}-{appName}";
builder.Host = string.Join(".", parts);
if (fragment != null)
{
builder.Fragment = fragment;
}
return builder.ToString();
var capabilities = await FetchJSONAsync<object, Capabilities>(
method: HttpMethod.Get,
path: "settings/capabilities",
body: null,
hasResponse: true
);

var cozyURL = GetCozyURL();
var subdomainKey = "flat_subdomains";
var subdomain = capabilities.Data.Attributes.ContainsKey(subdomainKey) && capabilities.Data.Attributes[subdomainKey] ? "flat" : "nested";

var link = UrlHelper.GenerateWebLink(
cozyUrl: cozyURL,
searchParams: null,
pathname: "",
hash: "/vault?action=import",
slug: "passwords",
subDomainType: subdomain
);

return link;
}

private string GenerateRandomValue() {
Expand Down Expand Up @@ -290,3 +302,25 @@ public string GetRegistrationURL(string lang)
}
}
}

public class Capabilities
{

[JsonProperty("data")]
public CapabilitiesData Data { get; set; }
}

public class CapabilitiesData
{
[JsonProperty("type")]
public string Type { get; set; }

[JsonProperty("id")]
public string Id { get; set; }

[JsonProperty("attributes")]
public Dictionary<string, bool> Attributes { get; set; }

[JsonProperty("links")]
public Dictionary<string, string> Links { get; set; }
}
81 changes: 81 additions & 0 deletions src/Core/Utilities/UrlHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Flurl;

namespace Bit.Core.Utilities
{
public static class UrlHelper
{
// Code taken from CozyClient.EnsureFirstSlash()
private static string EnsureFirstSlash(string path)
{
if (string.IsNullOrEmpty(path))
{
return "/";
}
else
{
return path.StartsWith("/") ? path : "/" + path;
}
}

/// <summary>
/// Construct a link to a web app
///
/// This function does not get its cozy url from a CozyClient instance so it can
/// be used to build urls that point to other Cozies than the user's own Cozy.
/// This is useful when pointing to the Cozy of the owner of a shared note for
/// example.
///
/// Code taken from CozyClient.generateWebLink()
/// </summary>
/// <param name="cozyUrl">Base URL of the cozy, eg. cozy.tools or test.mycozy.cloud</param>
/// <param name="searchParams">Dictionary of search parameters as {key, value}, eg. {'username', 'bob'}</param>
/// <param name="pathname">Path to a specific part of the app, eg. /public</param>
/// <param name="hash">Path inside the app, eg. /files/test.jpg</param>
/// <param name="slug">Slug of the app</param>
/// <param name="subDomainType">Whether the cozy is using flat or nested subdomains. Defaults to flat.</param>
/// <returns>Generated URL</returns>
public static string GenerateWebLink(
string cozyUrl,
Dictionary<string, string> searchParams,
string pathname,
string hash,
string slug,
string subDomainType
)
{
if (searchParams == null)
{
searchParams = new Dictionary<string, string>();
}

var url = new Url(cozyUrl);

if (subDomainType == "nested")
{
url.Host = $"{slug}.{url.Host}";
}
else
{
var hostParts = url.Host
.Split('.')
.Select((x, i) => i == 0 ? x + '-' + slug : x);

url.Host = string.Join(".", hostParts);
}

url.Path = EnsureFirstSlash(pathname);
url.Fragment = EnsureFirstSlash(hash);

foreach (var entry in searchParams)
{
url.QueryParams.Add(entry.Key, entry.Value);
}

return url.ToString();
}
}
}
62 changes: 62 additions & 0 deletions test/Core.Test/Utilities/UrlHelperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Collections.Generic;
using Bit.Core.Utilities;
using Xunit;

namespace Bit.Core.Test.Utilities
{
public class UrlHelperTests
{
[Fact]
public void GenerateWebLink_GeneratesTheRightLinkToAFlatCozy()
{
var sharecode = "sharingIsCaring";
var username = "alice";

var webLink = UrlHelper.GenerateWebLink(
cozyUrl: "http://alice.cozy.tools",
searchParams: new Dictionary<string, string>()
{
{ "sharecode", sharecode },
{ "username", username },
},
pathname: "public",
hash: "/n/4",
slug: "notes",
subDomainType: "flat"
);

Assert.Equal($"http://alice-notes.cozy.tools/public?sharecode={sharecode}&username={username}#/n/4", webLink);
}

[Fact]
public void GenerateWebLink_GeneratesTheRightLinkToANestedCozy()
{
var webLink = UrlHelper.GenerateWebLink(
cozyUrl: "https://alice.cozy.tools",
searchParams: null,
pathname: "/public/",
hash: "files/432432",
slug: "drive",
subDomainType: "nested"
);

Assert.Equal("https://drive.alice.cozy.tools/public/#/files/432432", webLink);
}

[Fact]
public void GenerateWebLink_CorrectlySetsSlashBeforeFragmentIfNoPath()
{
// Even if "/" is not mandatory in URL before the Hash, we want to be iso with CozyClient's behavior
var webLink = UrlHelper.GenerateWebLink(
cozyUrl: "https://alice.cozy.tools",
searchParams: null,
pathname: "",
hash: "/vault?action=import",
slug: "passwords",
subDomainType: "flat"
);

Assert.Equal("https://alice-passwords.cozy.tools/#/vault?action=import", webLink);
}
}
}

0 comments on commit f9cb62f

Please sign in to comment.