Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Mac OS X Sandboxing #169

Merged
merged 58 commits into from
Feb 14, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
b893a73
Add OS X and iOS utility method file.
rryan Jan 16, 2014
97791eb
Add Security and CoreServices frameworks.
rryan Jan 24, 2014
e039bd1
Update OS X entitlements.
rryan Jan 24, 2014
1d246c0
Add QStringToCFString helper method.
rryan Jan 24, 2014
7d7cb57
Add TrackInfoObject::getCanonicalLocation method.
rryan Jan 24, 2014
5a9aaa9
Always codesign even if the files haven't changed.
rryan Jan 24, 2014
984eefe
Minor style.
rryan Jan 24, 2014
4f4452e
Add notes about sandboxing to BasePlaylistFeature, CrateFeature, and …
rryan Jan 24, 2014
9e7add5
Add mac.cpp to depends.py.
rryan Jan 24, 2014
b50a508
Add Sandbox, a singleton class for managing sandbox-related operations.
rryan Jan 24, 2014
14ca80b
Load sandbox configuration at bootup and save it at shutdown.
rryan Jan 24, 2014
e5b772c
Delete sandbox config in ~Sandbox().
rryan Jan 24, 2014
2d99386
Add sandbox support to the Library.
rryan Jan 26, 2014
271f93a
Create a sandbox security token for files opened via the menu shortcut.
rryan Jan 26, 2014
4f0a488
Create a sandbox security token when the user picks a new recordings …
rryan Jan 26, 2014
7104fee
Add simple wrappers around QFile and QDir that open/close a security …
rryan Jan 26, 2014
d6f0e32
Support creating sandbox tokens on iTunes and Traktor library selection.
rryan Jan 26, 2014
73ab535
Add copy constructors to MFile and MDir.
rryan Jan 26, 2014
885de1a
As a fallback (because by this point we should already have gotten ac…
rryan Jan 26, 2014
aeab154
Only add quick links that we have access to.
rryan Jan 26, 2014
bea0744
Open and close a sandbox token while a SoundSource is active.
rryan Jan 26, 2014
f9c9473
Acquire a sandbox token while BrowseTableModel is viewing a directory.
rryan Jan 26, 2014
d735533
Use a security token while evaluating folder children.
rryan Jan 26, 2014
eca098e
Don't try to open the quick link or device node in the BrowseThread.
rryan Jan 26, 2014
066fc00
Acquire a security bookmark for every top-level directory we scan.
rryan Jan 26, 2014
205741f
Forgot file.h include.
rryan Jan 26, 2014
6432581
On second thought, the SoundSourceProxy should grab the security token.
rryan Jan 26, 2014
f3d5aaa
Convert Sandbox from a Singleton to a static class.
rryan Jan 26, 2014
3f57a1f
Use reference counting for SandboxSecurityToken.
rryan Jan 26, 2014
41b3685
Add getter for security token to MDir/MFile.
rryan Jan 26, 2014
f90b118
Open a bookmark when evaluating whether a folder has children.
rryan Jan 26, 2014
8aeba58
Add useful debug message.
rryan Jan 26, 2014
044bec6
Remove unused method.
rryan Jan 26, 2014
bdb41f1
Add a security token cache to avoid repeated (slow) opening and closi…
rryan Jan 26, 2014
931645d
Prevent any security bookmark opening/closing when scaning the library.
rryan Jan 26, 2014
59fdb07
Remove odd special-casing of /.
rryan Jan 26, 2014
9bdea99
Make Sandbox::canAccessPath public.
rryan Jan 26, 2014
47e7c22
Add canAccess helper to MFile and MDir.
rryan Jan 26, 2014
ba72e0f
Pass security token directly to BrowseThread to prevent excessive ope…
rryan Jan 26, 2014
9acfa08
Pass MDir directly to BrowseTableModel to prevent excessive opening/c…
rryan Jan 26, 2014
337ab14
Fix build.
rryan Jan 27, 2014
d38532f
Some cleanups.
rryan Jan 27, 2014
789f98d
Pass token in DlgTrackInfo.
rryan Jan 28, 2014
bdb9ed9
Add comment.
rryan Jan 28, 2014
1c7b838
Use sandbox security bookmarks in AudioTagger.
rryan Jan 28, 2014
47970a1
Also re-use security token when cloning TrackInfoObject.
rryan Jan 28, 2014
3fa83e6
Handle creation of scoped bookmarks on drop of files in Mixxx.
rryan Jan 29, 2014
c6fb4f0
Address review comments.
rryan Jan 29, 2014
660f50f
Change TrackInfoObject constructor parameter order.
rryan Jan 29, 2014
761dfca
Fix VAMP and SoundSource plugin-path hardcoding of "Library/Applicati…
rryan Jan 29, 2014
59e3168
Make askForAccess return true if sandbox is not enabled.
rryan Jan 29, 2014
59e2a71
Add missing semicolon.
rryan Feb 10, 2014
961d41c
tabs->spaces in soundsourceproxy.cpp
rryan Feb 10, 2014
a5f8391
Use DND helper for WTrackText / WTrackProperty. Add new DND helper me…
rryan Feb 11, 2014
b60533d
Misc. fixes.
rryan Feb 12, 2014
f33eef3
Use QFileInfo::isReadable() instead of access() from unistd.h.
rryan Feb 14, 2014
0fdac82
Improve the formatting of the askForAccess dialog.
rryan Feb 14, 2014
1da742e
Codesign plugins before binaries.
rryan Feb 14, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@ def configure(self, build, conf):
raise Exception('Did not find GLU development files')


class SecurityFramework(Dependence):
"""The iOS/OS X security framework is used to implement sandboxing."""
def configure(self, build, conf):
if not build.platform_is_osx:
return
build.env.Append(CPPPATH='/System/Library/Frameworks/Security.framework/Headers/')
build.env.Append(LINKFLAGS='-framework Security')


class CoreServices(Dependence):
def configure(self, build, conf):
if not build.platform_is_osx:
return
build.env.Append(CPPPATH='/System/Library/Frameworks/CoreServices.framework/Headers/')
build.env.Append(LINKFLAGS='-framework CoreServices')


class OggVorbis(Dependence):

def configure(self, build, conf):
Expand Down Expand Up @@ -844,6 +861,9 @@ def sources(self, build):
"util/version.cpp",
"util/rlimit.cpp",
"util/valuetransformer.cpp",
"util/sandbox.cpp",
"util/file.cpp",
"util/mac.cpp",

'#res/mixxx.qrc'
]
Expand Down Expand Up @@ -1034,7 +1054,7 @@ def configure(self, build, conf):
def depends(self, build):
return [SoundTouch, ReplayGain, PortAudio, PortMIDI, Qt,
FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib, ProtoBuf,
Chromaprint, RubberBand]
Chromaprint, RubberBand, SecurityFramework, CoreServices]

def post_dependency_check_configure(self, build, conf):
"""Sets up additional things in the Environment that must happen
Expand Down
10 changes: 6 additions & 4 deletions build/osx/OSConsX.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,14 +476,16 @@ def do_codesign(target, source, env):
# Don't descend.
del dirs[:]

# Codesign binaries.
for root, dirs, files in os.walk(binary_path):
for filename in files:
codesign_path(application_identity, keychain, entitlements, os.path.join(root, filename))
# Codesign plugins.
for root, dirs, files in os.walk(plugins_path):
for filename in files:
codesign_path(application_identity, keychain, entitlements, os.path.join(root, filename))

# Codesign binaries.
for root, dirs, files in os.walk(binary_path):
for filename in files:
codesign_path(application_identity, keychain, entitlements, os.path.join(root, filename))

# Codesign the bundle.
codesign_path(application_identity, keychain, entitlements, bundle)
CodeSign = Builder(action = do_codesign)
Expand Down
8 changes: 4 additions & 4 deletions build/osx/entitlements.plist
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<true/>
<key>com.apple.security.assets.music.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.device.firewire</key>
<true/>
<key>com.apple.security.device.microphone</key>
Expand All @@ -16,9 +18,7 @@
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.temporary-exception.files.absolute-path.read-write</key>
<array>
<string>/</string>
</array>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>
</dict>
</plist>
2 changes: 1 addition & 1 deletion src/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,8 @@ if build.platform_is_osx and 'bundle' in COMMAND_LINE_TARGETS:
CODESIGN_KEYCHAIN=codesign_keychain,
CODESIGN_KEYCHAIN_PASSWORD=codesign_keychain_password,
CODESIGN_ENTITLEMENTS=codesign_entitlements)
env.Alias('sign', codesign)
env.AlwaysBuild(codesign)
env.Alias('sign', codesign)

package_name = 'mixxx'
package_version = osx_construct_version(build, mixxx_version, branch_name, vcs_revision)
Expand Down
38 changes: 22 additions & 16 deletions src/audiotagger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
#include <taglib/wavfile.h>
#include <taglib/textidentificationframe.h>

AudioTagger::AudioTagger(QString file)
: m_file(file) {
AudioTagger::AudioTagger(const QString& file, SecurityTokenPointer pToken)
: m_file(file),
m_pSecurityToken(pToken.isNull() ? Sandbox::openSecurityToken(
m_file, true) : pToken) {
}

AudioTagger::~AudioTagger() {
Expand Down Expand Up @@ -79,8 +81,11 @@ void AudioTagger::setTracknumber(QString tracknumber) {
bool AudioTagger::save() {
TagLib::File* file = NULL;

if (m_file.endsWith(".mp3", Qt::CaseInsensitive)) {
file = new TagLib::MPEG::File(m_file.toLocal8Bit().constData());
const QString& filePath = m_file.canonicalFilePath();
QByteArray fileBA = filePath.toLocal8Bit();

if (filePath.endsWith(".mp3", Qt::CaseInsensitive)) {
file = new TagLib::MPEG::File(fileBA.constData());
// process special ID3 fields, APEv2 fiels, etc

// If the mp3 has no ID3v2 tag, we create a new one and add the TBPM and TKEY frame
Expand All @@ -89,35 +94,36 @@ bool AudioTagger::save() {
addAPETag(((TagLib::MPEG::File*) file)->APETag(false));
}

if (m_file.endsWith(".m4a", Qt::CaseInsensitive)) {
file = new TagLib::MP4::File(m_file.toLocal8Bit().constData());
if (filePath.endsWith(".m4a", Qt::CaseInsensitive)) {
file = new TagLib::MP4::File(fileBA.constData());
// process special ID3 fields, APEv2 fiels, etc
processMP4Tag(((TagLib::MP4::File*) file)->tag());

}
if (m_file.endsWith(".ogg", Qt::CaseInsensitive)) {
file = new TagLib::Ogg::Vorbis::File(m_file.toLocal8Bit().constData());
if (filePath.endsWith(".ogg", Qt::CaseInsensitive)) {
file = new TagLib::Ogg::Vorbis::File(fileBA.constData());
// process special ID3 fields, APEv2 fiels, etc
addXiphComment(((TagLib::Ogg::Vorbis::File*)file)->tag());

}
if (m_file.endsWith(".wav", Qt::CaseInsensitive)) {
file = new TagLib::RIFF::WAV::File(m_file.toLocal8Bit().constData());
if (filePath.endsWith(".wav", Qt::CaseInsensitive)) {
file = new TagLib::RIFF::WAV::File(fileBA.constData());
//If the flac has no ID3v2 tag, we create a new one and add the TBPM and TKEY frame
addID3v2Tag(((TagLib::RIFF::WAV::File*)file)->tag());

}
if (m_file.endsWith(".flac", Qt::CaseInsensitive)) {
file = new TagLib::FLAC::File(m_file.toLocal8Bit().constData());
if (filePath.endsWith(".flac", Qt::CaseInsensitive)) {
file = new TagLib::FLAC::File(fileBA.constData());

//If the flac has no ID3v2 tag, we create a new one and add the TBPM and TKEY frame
addID3v2Tag(((TagLib::FLAC::File*)file)->ID3v2Tag(true) );
//If the flac has no APE tag, we create a new one and add the TBPM and TKEY frame
addXiphComment(((TagLib::FLAC::File*) file)->xiphComment(true));

}
if (m_file.endsWith(".aif", Qt::CaseInsensitive) || m_file.endsWith(".aiff", Qt::CaseInsensitive)) {
file = new TagLib::RIFF::AIFF::File(m_file.toLocal8Bit().constData());
if (filePath.endsWith(".aif", Qt::CaseInsensitive) ||
filePath.endsWith(".aiff", Qt::CaseInsensitive)) {
file = new TagLib::RIFF::AIFF::File(fileBA.constData());
//If the flac has no ID3v2 tag, we create a new one and add the TBPM and TKEY frame
addID3v2Tag(((TagLib::RIFF::AIFF::File*)file)->tag());

Expand All @@ -143,9 +149,9 @@ bool AudioTagger::save() {
//write audio tags to file
int success = file->save();
if (success) {
qDebug() << "Successfully updated metadata of track " << m_file;
qDebug() << "Successfully updated metadata of track " << filePath;
} else {
qDebug() << "Could not update metadata of track " << m_file;
qDebug() << "Could not update metadata of track " << filePath;
}
//delete file and return
delete file;
Expand Down
19 changes: 9 additions & 10 deletions src/audiotagger.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
#define AUDIOTAGGER_H

#include <QString>
#include <QFileInfo>
#include <taglib/apetag.h>
#include <taglib/id3v2tag.h>
#include <taglib/xiphcomment.h>
#include <taglib/mp4tag.h>

#include "util/sandbox.h"


class AudioTagger
{
public:


AudioTagger (QString file);
class AudioTagger {
public:
AudioTagger(const QString& file, SecurityTokenPointer pToken);
virtual ~AudioTagger ( );

void setArtist (QString artist );
void setTitle (QString title );
void setAlbum (QString album );
Expand All @@ -31,8 +30,7 @@ class AudioTagger
void setTracknumber (QString tracknumber );
bool save();


private:
private:
QString m_artist;
QString m_title;
QString m_albumArtist;
Expand All @@ -46,7 +44,8 @@ class AudioTagger
QString m_bpm;
QString m_tracknumber;

QString m_file;
QFileInfo m_file;
SecurityTokenPointer m_pSecurityToken;

/** adds or modifies the ID3v2 tag to include BPM and KEY information **/
void addID3v2Tag(TagLib::ID3v2::Tag* id3v2);
Expand Down
7 changes: 7 additions & 0 deletions src/basetrackplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "track/beatgrid.h"
#include "waveform/renderers/waveformwidgetrenderer.h"
#include "analyserqueue.h"
#include "util/sandbox.h"

BaseTrackPlayer::BaseTrackPlayer(QObject* pParent,
ConfigObject<ConfigValue>* pConfig,
Expand Down Expand Up @@ -101,6 +102,12 @@ BaseTrackPlayer::~BaseTrackPlayer()
}

void BaseTrackPlayer::slotLoadTrack(TrackPointer track, bool bPlay) {
// Before loading the track, ensure we have access. This uses lazy
// evaluation to make sure track isn't NULL before we dereference it.
if (!track.isNull() && !Sandbox::askForAccess(track->getCanonicalLocation())) {
// We don't have access.
return;
}

//Disconnect the old track's signals.
if (m_pLoadedTrack) {
Expand Down
16 changes: 13 additions & 3 deletions src/dlgprefrecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "controlobject.h"
#include "encoder/encoder.h"
#include "controlobjectthread.h"
#include "util/sandbox.h"

DlgPrefRecord::DlgPrefRecord(QWidget* parent, ConfigObject<ConfigValue>* pConfig)
: DlgPreferencePage(parent),
Expand Down Expand Up @@ -231,10 +232,19 @@ void DlgPrefRecord::slotUpdate() {
}

void DlgPrefRecord::slotBrowseRecordingsDir() {
QString fd = QFileDialog::getExistingDirectory(this, tr("Choose recordings directory"),
m_pConfig->getValueString(
ConfigKey(RECORDING_PREF_KEY, "Directory")));
QString fd = QFileDialog::getExistingDirectory(
this, tr("Choose recordings directory"),
m_pConfig->getValueString(ConfigKey(RECORDING_PREF_KEY,
"Directory")));

if (fd != "") {
// The user has picked a new directory via a file dialog. This means the
// system sandboxer (if we are sandboxed) has granted us permission to
// this folder. Create a security bookmark while we have permission so
// that we can access the folder on future runs. We need to canonicalize
// the path so we first wrap the directory string with a QDir.
QDir directory(fd);
Sandbox::createSecurityToken(directory);
LineEditRecordings->setText(fd);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/dlgtrackinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,8 @@ void DlgTrackInfo::slotBpmTap() {

void DlgTrackInfo::reloadTrackMetadata() {
if (m_pLoadedTrack) {
TrackPointer pTrack(new TrackInfoObject(m_pLoadedTrack->getLocation()));
TrackPointer pTrack(new TrackInfoObject(m_pLoadedTrack->getLocation(),
m_pLoadedTrack->getSecurityToken()));
populateFields(pTrack);
}
}
Expand Down
11 changes: 2 additions & 9 deletions src/library/analysisfeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "mixxxkeyboard.h"
#include "analyserqueue.h"
#include "soundsourceproxy.h"
#include "util/dnd.h"

const QString AnalysisFeature::m_sAnalysisViewName = QString("Analysis");

Expand Down Expand Up @@ -134,15 +135,7 @@ void AnalysisFeature::cleanupAnalyser() {

bool AnalysisFeature::dropAccept(QList<QUrl> urls, QObject* pSource) {
Q_UNUSED(pSource);
QList<QFileInfo> files;
foreach (QUrl url, urls) {
// XXX: Possible WTF alert - Previously we thought we needed toString() here
// but what you actually want in any case when converting a QUrl to a file
// system path is QUrl::toLocalFile(). This is the second time we have
// flip-flopped on this, but I think toLocalFile() should work in any
// case. toString() absolutely does not work when you pass the result to a
files.append(url.toLocalFile());
}
QList<QFileInfo> files = DragAndDropHelper::supportedTracksFromUrls(urls, false, true);
// Adds track, does not insert duplicates, handles unremoving logic.
QList<int> trackIds = m_pTrackCollection->getTrackDAO().addTracks(files, true);
analyzeTracks(trackIds);
Expand Down
16 changes: 5 additions & 11 deletions src/library/autodjfeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "widget/wlibrary.h"
#include "mixxxkeyboard.h"
#include "soundsourceproxy.h"
#include "util/dnd.h"

const QString AutoDJFeature::m_sAutoDJViewName = QString("Auto DJ");

Expand Down Expand Up @@ -119,17 +120,10 @@ bool AutoDJFeature::dropAccept(QList<QUrl> urls, QObject* pSource) {
//TODO: Filter by supported formats regex and reject anything that doesn't match.
TrackDAO &trackDao = m_pTrackCollection->getTrackDAO();

//If a track is dropped onto a playlist's name, but the track isn't in the library,
//then add the track to the library before adding it to the playlist.
QList<QFileInfo> files;
foreach (QUrl url, urls) {
//XXX: See the note in PlaylistFeature::dropAccept() about using QUrl::toLocalFile()
// instead of toString()
QFileInfo file = url.toLocalFile();
if (SoundSourceProxy::isFilenameSupported(file.fileName())) {
files.append(file);
}
}
// If a track is dropped onto a playlist's name, but the track isn't in the
// library, then add the track to the library before adding it to the
// playlist.
QList<QFileInfo> files = DragAndDropHelper::supportedTracksFromUrls(urls, false, true);
QList<int> trackIds;
if (pSource) {
trackIds = trackDao.getTrackIds(files);
Expand Down
10 changes: 10 additions & 0 deletions src/library/baseplaylistfeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ void BasePlaylistFeature::slotImportPlaylist() {
m_pConfig->set(ConfigKey("[Library]","LastImportExportPlaylistDirectory"),
ConfigValue(fileName.dir().absolutePath()));

// The user has picked a new directory via a file dialog. This means the
// system sandboxer (if we are sandboxed) has granted us permission to this
// folder. We don't need access to this file on a regular basis so we do not
// register a security bookmark.

Parser* playlist_parser = NULL;

if (playlist_file.endsWith(".m3u", Qt::CaseInsensitive) ||
Expand Down Expand Up @@ -357,6 +362,11 @@ void BasePlaylistFeature::slotExportPlaylist() {
m_pConfig->set(ConfigKey("[Library]","LastImportExportPlaylistDirectory"),
ConfigValue(fileName.dir().absolutePath()));

// The user has picked a new directory via a file dialog. This means the
// system sandboxer (if we are sandboxed) has granted us permission to this
// folder. We don't need access to this file on a regular basis so we do not
// register a security bookmark.

// Create a new table model since the main one might have an active search.
// This will only export songs that we think exist on default
QScopedPointer<PlaylistTableModel> pPlaylistTableModel(
Expand Down
3 changes: 2 additions & 1 deletion src/library/basesqltablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "playerinfo.h"
#include "track/keyutils.h"
#include "util/time.h"
#include "util/dnd.h"

const bool sDebug = false;

Expand Down Expand Up @@ -852,7 +853,7 @@ QMimeData* BaseSqlTableModel::mimeData(const QModelIndexList &indexes) const {
continue;
}
rows.insert(index.row());
QUrl url = QUrl::fromLocalFile(getTrackLocation(index));
QUrl url = DragAndDropHelper::urlFromLocation(getTrackLocation(index));
if (!url.isValid()) {
qDebug() << this << "ERROR: invalid url" << url;
continue;
Expand Down
Loading