Skip to content

Commit

Permalink
fix: Prevent using a default Firefox profile with the `--keep-profile…
Browse files Browse the repository at this point in the history
…-changes` option (#1007)
  • Loading branch information
rpl authored and kumar303 committed Aug 1, 2017
1 parent 65d9459 commit 532951f
Show file tree
Hide file tree
Showing 2 changed files with 305 additions and 0 deletions.
95 changes: 95 additions & 0 deletions src/firefox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,90 @@ export async function run(
}


// isDefaultProfile types and implementation.

const DEFAULT_PROFILES_NAMES = [
'default',
'dev-edition-default',
];

export type IsDefaultProfileFn = (
profilePathOrName: string,
ProfileFinder?: typeof FirefoxProfile.Finder,
fsStat?: typeof fs.stat,
) => Promise<boolean>;

/*
* Tests if a profile is a default Firefox profile (both as a profile name or
* profile path).
*
* Returns a promise that resolves to true if the profile is one of default Firefox profile.
*/
export async function isDefaultProfile(
profilePathOrName: string,
ProfileFinder?: typeof FirefoxProfile.Finder = FirefoxProfile.Finder,
fsStat?: typeof fs.stat = fs.stat,
): Promise<boolean> {
if (DEFAULT_PROFILES_NAMES.includes(profilePathOrName)) {
return true;
}

const baseProfileDir = ProfileFinder.locateUserDirectory();
const profilesIniPath = path.join(baseProfileDir, 'profiles.ini');
try {
await fsStat(profilesIniPath);
} catch (error) {
if (isErrorWithCode('ENOENT', error)) {
log.debug(`profiles.ini not found: ${error}`);

// No profiles exist yet, default to false (the default profile name contains a
// random generated component).
return false;
}

// Re-throw any unexpected exception.
throw error;
}

// Check for profile dir path.
const finder = new ProfileFinder(baseProfileDir);
const readProfiles = promisify(finder.readProfiles, finder);

await readProfiles();

const normalizedProfileDirPath = path.normalize(
path.join(path.resolve(profilePathOrName), path.sep)
);

for (const profile of finder.profiles) {
// Check if the profile dir path or name is one of the default profiles
// defined in the profiles.ini file.
if (DEFAULT_PROFILES_NAMES.includes(profile.Name) ||
profile.Default === '1') {
let profileFullPath;

// Check for profile name.
if (profile.Name === profilePathOrName) {
return true;
}

// Check for profile path.
if (profile.IsRelative === '1') {
profileFullPath = path.join(baseProfileDir, profile.Path, path.sep);
} else {
profileFullPath = path.join(profile.Path, path.sep);
}

if (path.normalize(profileFullPath) === normalizedProfileDirPath) {
return true;
}
}
}

// Profile directory not found.
return false;
}

// configureProfile types and implementation.

export type ConfigureProfileOptions = {|
Expand Down Expand Up @@ -238,6 +322,7 @@ export function configureProfile(
export type UseProfileParams = {
app?: PreferencesAppName,
configureThisProfile?: ConfigureProfileFn,
isFirefoxDefaultProfile?: IsDefaultProfileFn,
customPrefs?: FirefoxPreferences,
};

Expand All @@ -248,9 +333,19 @@ export async function useProfile(
{
app,
configureThisProfile = configureProfile,
isFirefoxDefaultProfile = isDefaultProfile,
customPrefs = {},
}: UseProfileParams = {},
): Promise<FirefoxProfile> {
const isForbiddenProfile = await isFirefoxDefaultProfile(profilePath);
if (isForbiddenProfile) {
throw new UsageError(
'Cannot use --keep-profile-changes on a default profile' +
` ("${profilePath}")` +
' because web-ext will make it insecure and unsuitable for daily use.' +
'\nSee https://github.com/mozilla/web-ext/issues/1005'
);
}
const profile = new FirefoxProfile({destinationDirectory: profilePath});
return await configureThisProfile(profile, {app, customPrefs});
}
Expand Down
210 changes: 210 additions & 0 deletions tests/unit/test-firefox/test.firefox.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,38 @@ function withBaseProfile(callback) {
);
}

function createFakeProfileFinder(profilesDirPath) {
const FakeProfileFinder = sinon.spy((...args) => {
const finder = new FirefoxProfile.Finder(...args);

sinon.spy(finder, 'readProfiles');

return finder;
});

FakeProfileFinder.locateUserDirectory = sinon.spy(() => {
return profilesDirPath;
});

return FakeProfileFinder;
}

async function createFakeProfilesIni(
dirPath: string, profilesDefs: Array<Object>
): Promise<void> {
let content = '';

for (const [idx, profile] of profilesDefs.entries()) {
content += `[Profile${idx}]\n`;
for (const k of Object.keys(profile)) {
content += `${k}=${profile[k]}\n`;
}
content += '\n';
}

await fs.writeFile(path.join(dirPath, 'profiles.ini'), content);
}

describe('firefox', () => {

describe('run', () => {
Expand Down Expand Up @@ -263,6 +295,160 @@ describe('firefox', () => {

});

describe('isDefaultProfile', () => {

it('detects common Firefox default profiles specified by name',
async () => {
const isDefault = await firefox.isDefaultProfile('default');
assert.equal(isDefault, true);

const isDevEditionDefault = await firefox.isDefaultProfile(
'dev-edition-default'
);
assert.equal(isDevEditionDefault, true);
});

it('allows profile name if it is not listed as default in profiles.ini',
async () => {
return withTempDir(async (tmpDir) => {
const profilesDirPath = tmpDir.path();
const FakeProfileFinder = createFakeProfileFinder(profilesDirPath);

await createFakeProfilesIni(profilesDirPath, [
{
Name: 'manually-set-default',
Path: 'fake-default-profile',
IsRelative: 1,
Default: 1,
},
]);

const isDefault = await firefox.isDefaultProfile(
'manually-set-default', FakeProfileFinder
);
assert.equal(
isDefault, true,
'Manually configured default profile'
);

const isNotDefault = await firefox.isDefaultProfile(
'unkown-profile-name', FakeProfileFinder
);
assert.equal(
isNotDefault, false,
'Unknown profile name'
);
});
});

it('allows profile path if it is not listed as default in profiles.ini',
async () => {
return withTempDir(async (tmpDir) => {
const profilesDirPath = tmpDir.path();
const FakeProfileFinder = createFakeProfileFinder(profilesDirPath);
const absProfilePath = path.join(
profilesDirPath,
'fake-manually-default-profile'
);

await createFakeProfilesIni(profilesDirPath, [
{
Name: 'default',
Path: 'fake-default-profile',
IsRelative: 1,
},
{
Name: 'dev-edition-default',
Path: 'fake-devedition-default-profile',
IsRelative: 1,
},
{
Name: 'manually-set-default',
Path: absProfilePath,
Default: 1,
},
]);

const isFirefoxDefaultPath = await firefox.isDefaultProfile(
path.join(profilesDirPath, 'fake-default-profile'),
FakeProfileFinder
);
assert.equal(
isFirefoxDefaultPath, true,
'Firefox default profile'
);

const isDevEditionDefaultPath = await firefox.isDefaultProfile(
path.join(profilesDirPath, 'fake-devedition-default-profile'),
FakeProfileFinder
);
assert.equal(
isDevEditionDefaultPath, true,
'Firefox DevEdition default profile'
);

const isManuallyDefault = await firefox.isDefaultProfile(
absProfilePath,
FakeProfileFinder
);
assert.equal(
isManuallyDefault, true,
'Manually configured default profile'
);

const isNotDefault = await firefox.isDefaultProfile(
path.join(profilesDirPath, 'unkown-profile-dir'),
FakeProfileFinder
);
assert.equal(
isNotDefault, false,
'Unknown profile path'
);
});
});

it('allows profile path if there is no profiles.ini file',
async () => {
return withTempDir(async (tmpDir) => {
const profilesDirPath = tmpDir.path();
const FakeProfileFinder = createFakeProfileFinder(profilesDirPath);

const isNotDefault = await firefox.isDefaultProfile(
'/tmp/my-custom-profile-dir',
FakeProfileFinder
);

assert.equal(isNotDefault, false);
});
});

it('rejects on any unexpected error while looking for profiles.ini',
async () => {
return withTempDir(async (tmpDir) => {
const profilesDirPath = tmpDir.path();
const FakeProfileFinder = createFakeProfileFinder(profilesDirPath);
const fakeFsStat = sinon.spy(() => {
return Promise.reject(new Error('Fake fs stat error'));
});

let exception;
try {
await firefox.isDefaultProfile(
'/tmp/my-custom-profile-dir',
FakeProfileFinder,
fakeFsStat
);
} catch (error) {
exception = error;
}

assert.match(exception && exception.message, /Fake fs stat error/);
});
}
);

});

describe('createProfile', () => {

it('resolves with a profile object', () => {
Expand Down Expand Up @@ -304,6 +490,30 @@ describe('firefox', () => {
});

describe('useProfile', () => {
it('rejects to a UsageError when used on a default Firefox profile',
async () => {
const configureThisProfile = sinon.spy(
(profile) => Promise.resolve(profile)
);
const isFirefoxDefaultProfile = sinon.spy(
() => Promise.resolve(true)
);
let exception;

try {
await firefox.useProfile('default', {
configureThisProfile,
isFirefoxDefaultProfile,
});
} catch (error) {
exception = error;
}

assert.match(
exception && exception.message,
/Cannot use --keep-profile-changes on a default profile/
);
});

it('resolves to a FirefoxProfile instance', () => withBaseProfile(
(baseProfile) => {
Expand Down

0 comments on commit 532951f

Please sign in to comment.