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

Support Mac OS X Sandboxing #169

merged 58 commits into from
Feb 14, 2014

Conversation

rryan
Copy link
Member

@rryan rryan commented Jan 27, 2014

This branch implements Apple's sandboxing and security scoped bookmarks. This fixes Bug #1257340 and should complete the Mac App Store blueprint.

I have a few more things to do but the code is in a pretty stable state so I'd appreciate some extra eyes or testers.

Summary:

  • Maintain a security bookmark while a TrackInfoObject or SoundSourceProxy is active for it.
  • Register security bookmarks every time we open a QFileDialog.
  • Provide wrapper classes for QFile and QDir that open bookmarks.
  • Provide an "ask for access" flow for letting users grant access when upgrading from a pre-sandbox library.
  • Fix browse mode to only add quick links we have access to.
  • Cache and reference-count active security bookmarks and only close it when the last reference expires.
  • Store bookmarks base64-encoded in a new config file "sandbox.cfg" in the settings path.

Cases currently handled:

  • Adding or relocating a library directory.
  • Loading a file to a deck.
  • Recording.
  • Changing the recording path.
  • Library scanner (only one bookmark open per library directory -- very efficient)
  • Browse feature (if you pick a volume that we don't have access to it starts the "ask for access" flow)
  • iTunes feature
  • Traktor feature (we have to ask for access to the Traktor folder on the first use).
  • AudioTagger
  • DlgTrackInfo
  • Creation of bookmarks for dropped files.
  • Vamp and SoundSource plugin paths use the right user-data location.

Migration:

  • On bootup, we check for permissions to every library directory. If we can't access one we prompt for access.
  • If the iTunes saved library path is not accessible, we ask for access.
  • As a last-chance, if a track is about to be loaded to a deck that we don't have access to we pop up the "ask for access" flow.

Still TODO:

  • Browse mode selection of volumes is still a little wonky.
  • Text in the ask-for-access flow needs tightening.
  • Playlist import -- ask for access to files that are not covered.

[1] https://developer.apple.com/library/mac/documentation/security/conceptual/AppSandboxDesignGuide/AboutAppSandbox/AboutAppSandbox.html#//apple_ref/doc/uid/TP40011183-CH1-SW1
[2] https://bugs.launchpad.net/mixxx/+bug/1257340
[3] https://blueprints.launchpad.net/mixxx/+spec/macappstore

@ywwg
Copy link
Member

ywwg commented Jan 27, 2014

I am going to go over this nearly-1000 line code review with a fine toothed comb, I tell you!

@rryan
Copy link
Member Author

rryan commented Jan 27, 2014

Hey, at least I deleted 116 lines!

@ywwg
Copy link
Member

ywwg commented Jan 27, 2014

Let me state for the record that this is a huge help for our Mac users 👍. At the very least, maybe I can try to build mixxx on my mac and try it out.

@rryan
Copy link
Member Author

rryan commented Jan 29, 2014

Drag and drop works now.

@@ -101,6 +102,11 @@
}

void BaseTrackPlayer::slotLoadTrack(TrackPointer track, bool bPlay) {
// Before loading the track, ensure we have access:
if (track && !Sandbox::askForAccess(track->getCanonicalLocation())) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you also check if track is not NULL to avoid segfaults when calling getCanonicalLocation. I get that it works because of lazy evaluation. Is this usually written like that?
Can't we have a small wrapper for askForAccess so that is accepts trackpointers. That would make it more readable the lazy evaluation is not super obvious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, this is a pretty common pattern of validating a pointer before dereferencing it. I think a helper would make sense if this was common but this is the only time we pass a trackpointer to askForAccess.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a comment what happens here for C++ newbies

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@kain88-de
Copy link
Member

Nice work rryan. I'm mostly nit-picking

Do we need to inform users that they might be asked for file access on individual files that are not in the library folder when they upgrade to 1.12? Ideally you should collect all those as well on the first start and ask for file permissions

Also your branch does not compile on linux

rc/util/sandbox.cpp: In static member function ‘static bool Sandbox::askForAccess(const QString&)’:
src/util/sandbox.cpp:80:33: warning: unused variable ‘button’ [-Wunused-variable]
     QMessageBox::StandardButton button = QMessageBox::question(
                                 ^
src/util/sandbox.cpp: In static member function ‘static bool Sandbox::createSecurityToken(const QString&, bool)’:
src/util/sandbox.cpp:138:5: error: ‘CFURLRef’ was not declared in this scope
     CFURLRef url = CFURLCreateWithFileSystemPath(
     ^
src/util/sandbox.cpp:138:14: error: expected ‘;’ before ‘url’
     CFURLRef url = CFURLCreateWithFileSystemPath(
              ^
src/util/sandbox.cpp:141:9: error: ‘url’ was not declared in this scope
     if (url) {
         ^
src/util/sandbox.cpp:142:9: error: ‘CFErrorRef’ was not declared in this scope
         CFErrorRef error = NULL;
         ^
src/util/sandbox.cpp:142:20: error: expected ‘;’ before ‘error’
         CFErrorRef error = NULL;
                    ^
src/util/sandbox.cpp:143:9: error: ‘CFDataRef’ was not declared in this scope
         CFDataRef bookmark = CFURLCreateBookmarkData(
         ^
src/util/sandbox.cpp:143:19: error: expected ‘;’ before ‘bookmark’
         CFDataRef bookmark = CFURLCreateBookmarkData(
                   ^
src/util/sandbox.cpp:146:22: error: ‘CFRelease’ was not declared in this scope
         CFRelease(url);
                      ^
src/util/sandbox.cpp:147:13: error: ‘bookmark’ was not declared in this scope
         if (bookmark) {
             ^
src/util/sandbox.cpp:149:76: error: ‘CFDataGetBytePtr’ was not declared in this scope
                     reinterpret_cast<const char*>(CFDataGetBytePtr(bookmark)),
                                                                            ^
src/util/sandbox.cpp:150:45: error: ‘CFDataGetLength’ was not declared in this scope
                     CFDataGetLength(bookmark));
                                             ^
src/util/sandbox.cpp:160:17: error: ‘error’ was not declared in this scope
             if (error != NULL) {
                 ^
src/util/sandbox.cpp:161:87: error: ‘CFErrorCopyDescription’ was not declared in this scope
                 qDebug() << "Error:" << CFStringToQString(CFErrorCopyDescription(error));
                                                                                       ^
src/util/sandbox.cpp:161:88: error: ‘CFStringToQString’ was not declared in this scope
                 qDebug() << "Error:" << CFStringToQString(CFErrorCopyDescription(error));
                                                                                        ^
src/util/sandbox.cpp: At global scope:
src/util/sandbox.cpp:277:22: warning: unused parameter ‘canonicalPath’ [-Wunused-parameter]
 SecurityTokenPointer Sandbox::openTokenFromBookmark(const QString& canonicalPath,
                      ^
scons: *** [lin64_build/util/sandbox.o] Error 1
scons: building terminated because of errors.

@@ -85,6 +86,18 @@
pConfig->getValueString(ConfigKey("[Library]","ShowTraktorLibrary"),"1").toInt()) {
addFeature(new TraktorFeature(this, m_pTrackCollection));
}

// On startup we need to check if all of the user's library folders are
// accessible to us. If the user is using a database from <1.12.0 with
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this go into src/upgrade.cpp ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, it seemed like sane thing to do at boot every time though in case they somehow lost their sandbox.cfg file (or we corrupted it or something).

@rryan
Copy link
Member Author

rryan commented Jan 29, 2014

Ok, playlist import won't work if we don't have permission to the files in the playlist. My plan is to get the files you don't have access to, then calculate the largest common prefix from their file paths to determine what directory to ask for access to. This is such a bad situation.

@esbrandt
Copy link
Contributor

OS X 10.8.5

scons
./mixxx --controllerDebug --developer --resourcePath res

Tracks do not load to deck, not by dnd from Mixxx library/Finder nor from the libraries context menu. Same with tracks from the iTunes library.

Debug [Main]: createSecurityToken "/Users/user/Music/The Bug - London Zoo (2008)/01 Angry (feat. Tippa Irie).mp3" false 
Warning [Main]: QSqlQuery::value: not positioned on a valid record
Debug [Main]: openSecurityToken QFileInfo "/Users/maxi/Music/The Bug - London Zoo (2008)/01 Angry (feat. Tippa Irie).mp3" true 
Debug [Main]: New BeatGrid 
Debug [Main]: Successfully deserialized BeatGrid 
Debug [Main]: m_sTracks.count() = 1 
Debug [Main]: Sandbox::askForAccess "/Users/user/Music/The Bug - London Zoo (2008)/01 Angry (feat. Tippa Irie).mp3" 

@rryan
Copy link
Member Author

rryan commented Jan 29, 2014

Tracks do not load to deck, not by dnd from Mixxx library/Finder nor from the libraries context menu. Same with tracks from the iTunes library.

Oops -- I broke that today. Fixed.

@rryan
Copy link
Member Author

rryan commented Jan 29, 2014

BTW -- to test the sandbox mode you have to bundle the app and sign it with Mixxx's developer private key (which only I and the build server have). Once the build server is back up I'll enable sandboxed builds for testing. In the meantime, if you test outside of a sandbox just check that things aren't broken (you know.. obscure use cases like loading tracks to a deck :P).

@rryan
Copy link
Member Author

rryan commented Feb 10, 2014

If there are no objections I'd like to merge this and fix any issues in master going forward. It works pretty well for me -- no sandbox security warnings in Console.app. Playlist import is the only major remaining TODO.

#ifndef SANDBOX_H
#define SANDBOX_H

#include <unistd.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this header is not included in msvs

@rryan
Copy link
Member Author

rryan commented Feb 10, 2014

I'll wait for #173 to prevent causing @loadstar81 any conflicts.

@esbrandt
Copy link
Contributor

BTW -- to test the sandbox mode you have to bundle the app and sign it with Mixxx's developer private key (which only I and the build server have).

To avoid possible misunderstandings: We wont be able to test any future changes on OSX once this got merged, unless we use a signed bundle from the build server?

@rryan
Copy link
Member Author

rryan commented Feb 11, 2014

To avoid possible misunderstandings: We wont be able to test any future changes on OSX once this got merged, unless we use a signed bundle from the build server?

Yea, that's right. :-/ you can test that it didn't break anything for non-sandboxed usage.

@esbrandt
Copy link
Contributor

What are the impacts for future contributions?
How will a contributor on OSX test his own changes?

Also Capt. Obvious asks: When will the build server return, and how can we help to make it happen?

@rryan
Copy link
Member Author

rryan commented Feb 11, 2014

Oh sorry -- I misunderstood you. This change only affects OS X builds that are sandboxed. Non-sandboxed builds will work as usual. So a developer who is working on changes unrelated to sandboxing can just build and run as usual.

Even if you create a .app bundle that doesn't enable sandboxing unless you run scons with the codesign build action. To test a change related to sandboxing the binary has to be signed with the Mixxx Mac Developer private key.

I can setup a build target for someone's branch so they can test but it will be annoying because they will have to wait for the build server to finish building each time.

We may need to do another fundraiser to buy new hardware for the build server :(.

@esbrandt
Copy link
Contributor

Thanks for clarification.

We may need to do another fundraiser to buy new hardware for the build server :(.

Why not. Better now then never.
People will understand if we lay out the issues with the current one, and how it currently limits the dev process. Any options for using SAAS instead dedicated hardware build server?

@rryan
Copy link
Member Author

rryan commented Feb 11, 2014

Why not. Better now then never.
People will understand if we lay out the issues with the current one, and how it currently limits the dev process. Any options for using SAAS instead dedicated hardware build server?

Yea, true. We ran the numbers on SAAS options. Since compiling Mixxx is very CPU-heavy it will be costly to use a service like Amazon AWS. VPS or a similar option also exceeds our current amount of donations per month so that one isn't sustainable.

* Clean up / unify processing of dropped URLs across all drop handlers.
* Support dropping of playlists on the analysis, playlists, crate, autodj, and
  track table sections of the library (previously was only supported in
  playlists).
…on Support/Mixxx". Instead use QDesktopStorage::DataLocation.
On OS X 10.9, codesign order matters more. We have to codesign the frameworks
and plugins first and then the binaries and bundle.
@rryan
Copy link
Member Author

rryan commented Feb 14, 2014

Merging -- will fix any issues directly in master. Thanks to all for reviewing.

rryan added a commit that referenced this pull request Feb 14, 2014
Support Mac OS X Sandboxing
@rryan rryan merged commit 9837aa2 into mixxxdj:master Feb 14, 2014
@rryan rryan deleted the sandbox branch April 10, 2016 20:49
m0dB pushed a commit to m0dB/mixxx that referenced this pull request Jan 21, 2024
Netlify: Fix discourse comments blocked by CSP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants