- clean working copy for leaving SVN
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "juce_tracktion_marketplace",
|
||||
"name": "JUCE Tracktion marketplace support",
|
||||
"version": "3.1.1",
|
||||
"description": "JUCE classes for online product authentication via the Tracktion marketplace.",
|
||||
"website": "http://www.juce.com/juce",
|
||||
"license": "GPL/Commercial",
|
||||
|
||||
"dependencies": [ { "id": "juce_data_structures", "version": "matching" },
|
||||
{ "id": "juce_cryptography", "version": "matching" } ],
|
||||
|
||||
"include": "juce_tracktion_marketplace.h",
|
||||
|
||||
"compile": [ { "file": "juce_tracktion_marketplace.cpp" } ],
|
||||
|
||||
"browse": [ "marketplace/*" ]
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if defined (JUCE_TRACKTION_MARKETPLACE_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE
|
||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've
|
||||
already included any other headers - just put it inside a file on its own, possibly with your config
|
||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
|
||||
header files that the compiler may be using.
|
||||
*/
|
||||
#error "Incorrect use of JUCE cpp file"
|
||||
#endif
|
||||
|
||||
// Your project must contain an AppConfig.h file with your project-specific settings in it,
|
||||
// and your header search path must make it accessible to the module's files.
|
||||
#include "AppConfig.h"
|
||||
|
||||
#include "juce_tracktion_marketplace.h"
|
||||
|
||||
namespace juce
|
||||
{
|
||||
#include "marketplace/juce_TracktionMarketplaceStatus.cpp"
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
#include "marketplace/juce_TracktionMarketplaceUnlockForm.cpp"
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_TRACKTION_MARKETPLACE_H_INCLUDED
|
||||
#define JUCE_TRACKTION_MARKETPLACE_H_INCLUDED
|
||||
|
||||
/**
|
||||
The Tracktion Marketplace module is a simple user-registration system for
|
||||
allowing you to build apps/plugins with features that are unlocked by a
|
||||
user having a suitable account on a webserver.
|
||||
|
||||
Although originally designed for use with products that are sold on the
|
||||
Tracktion Marketplace web-store, the module itself is fully open, and can
|
||||
be used to connect to your own web-store instead, if you implement your
|
||||
own compatible web-server back-end.
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
#include "../juce_cryptography/juce_cryptography.h"
|
||||
#include "../juce_data_structures/juce_data_structures.h"
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
#include "../juce_gui_extra/juce_gui_extra.h"
|
||||
#endif
|
||||
|
||||
namespace juce
|
||||
{
|
||||
#include "marketplace/juce_TracktionMarketplaceStatus.h"
|
||||
#include "marketplace/juce_TracktionMarketplaceServer.h"
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
#include "marketplace/juce_TracktionMarketplaceUnlockForm.h"
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#endif // JUCE_TRACKTION_MARKETPLACE_H_INCLUDED
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
Contains static utilities for generating key-files that can be unlocked by
|
||||
the TracktionMarketplaceStatus class.
|
||||
*/
|
||||
class JUCE_API TracktionMarketplaceKeyGeneration
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Generates the content of a key-file which can be sent to a user's machine to
|
||||
unlock a product.
|
||||
|
||||
The returned value is a block of text containing an RSA-encoded block, followed
|
||||
by some human-readable details. If you pass this block of text to
|
||||
TracktionMarketplaceStatus::applyKeyFile(), it will decrpyt it, and if the
|
||||
key matches and the machine numbers match, it will unlock that machine.
|
||||
|
||||
Typically the way you'd use this on a server would be to build a small executable
|
||||
that simply calls this method and prints the result, so that the webserver can
|
||||
use this as a reply to the product's auto-registration mechanism. The
|
||||
keyGenerationAppMain() function is an example of how to build such a function.
|
||||
|
||||
@see TracktionMarketplaceStatus
|
||||
*/
|
||||
static String JUCE_CALLTYPE generateKeyFile (const String& appName,
|
||||
const String& userEmail,
|
||||
const String& userName,
|
||||
const String& machineNumbers,
|
||||
const RSAKey& privateKey);
|
||||
|
||||
//==============================================================================
|
||||
/** This is a simple implementation of a key-generator that you could easily wrap in
|
||||
a command-line main() function for use on your server.
|
||||
|
||||
So for example you might use this in a command line app called "unlocker" and
|
||||
then call it like this:
|
||||
|
||||
unlocker MyGreatApp Joe_Bloggs joebloggs@foobar.com 1234abcd,95432ff 22d9aec92d986dd1,923ad49e9e7ff294c
|
||||
*/
|
||||
static inline int keyGenerationAppMain (int argc, char* argv[])
|
||||
{
|
||||
StringArray args;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
args.add (argv[i]);
|
||||
|
||||
if (args.size() != 5)
|
||||
{
|
||||
std::cout << "Requires 5 arguments: app-name username user-email machine-numbers private-key" << std::endl
|
||||
<< " app-name: name of the product being unlocked" << std::endl
|
||||
<< " user-email: user's email address" << std::endl
|
||||
<< " username: name of the user. Careful not to allow any spaces!" << std::endl
|
||||
<< " machine-numbers: a comma- or semicolon-separated list of all machine ID strings this user can run this product on (no whitespace between items!)" << std::endl
|
||||
<< " private-key: the RSA private key corresponding to the public key you've used in the app" << std::endl
|
||||
<< std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (! args[4].containsChar (','))
|
||||
{
|
||||
std::cout << "Not a valid RSA key!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << generateKeyFile (args[0], args[1], args[2], args[3], RSAKey (args[4])) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/* Note: there's a bit of light obfuscation in this code, just to make things
|
||||
a bit more annoying for crackers who try to reverse-engineer your binaries, but
|
||||
nothing particularly foolproof.
|
||||
*/
|
||||
|
||||
struct KeyFileUtils
|
||||
{
|
||||
static String encryptXML (const XmlElement& xml, RSAKey privateKey)
|
||||
{
|
||||
MemoryOutputStream text;
|
||||
text << xml.createDocument (String::empty, true);
|
||||
|
||||
BigInteger val;
|
||||
val.loadFromMemoryBlock (text.getMemoryBlock());
|
||||
|
||||
privateKey.applyToValue (val);
|
||||
|
||||
return val.toString (16);
|
||||
}
|
||||
|
||||
static String createKeyFile (String comment,
|
||||
const XmlElement& xml,
|
||||
RSAKey rsaPrivateKey)
|
||||
{
|
||||
String asHex ("#" + encryptXML (xml, rsaPrivateKey));
|
||||
|
||||
StringArray lines;
|
||||
lines.add (comment);
|
||||
lines.add (String::empty);
|
||||
|
||||
const int charsPerLine = 70;
|
||||
while (asHex.length() > 0)
|
||||
{
|
||||
lines.add (asHex.substring (0, charsPerLine));
|
||||
asHex = asHex.substring (charsPerLine);
|
||||
}
|
||||
|
||||
lines.add (String::empty);
|
||||
|
||||
return lines.joinIntoString ("\r\n");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static XmlElement decryptXML (String hexData, RSAKey rsaPublicKey)
|
||||
{
|
||||
BigInteger val;
|
||||
val.parseString (hexData, 16);
|
||||
|
||||
RSAKey key (rsaPublicKey);
|
||||
key.applyToValue (val);
|
||||
|
||||
ScopedPointer<XmlElement> xml (XmlDocument::parse (val.toMemoryBlock().toString()));
|
||||
|
||||
return xml != nullptr ? *xml : XmlElement("key");
|
||||
}
|
||||
|
||||
static XmlElement getXmlFromKeyFile (String keyFileText, RSAKey rsaPublicKey)
|
||||
{
|
||||
return decryptXML (keyFileText.fromLastOccurrenceOf ("#", false, false).trim(), rsaPublicKey);
|
||||
}
|
||||
|
||||
static StringArray getMachineNumbers (XmlElement xml)
|
||||
{
|
||||
StringArray numbers;
|
||||
numbers.addTokens (xml.getStringAttribute ("mach"), ",; ", String::empty);
|
||||
numbers.trim();
|
||||
numbers.removeEmptyStrings();
|
||||
return numbers;
|
||||
}
|
||||
|
||||
static String getLicensee (const XmlElement& xml) { return xml.getStringAttribute ("user"); }
|
||||
static String getEmail (const XmlElement& xml) { return xml.getStringAttribute ("email"); }
|
||||
static String getAppID (const XmlElement& xml) { return xml.getStringAttribute ("app"); }
|
||||
|
||||
struct KeyFileData
|
||||
{
|
||||
String licensee, email, appID;
|
||||
StringArray machineNumbers;
|
||||
};
|
||||
|
||||
static KeyFileData getDataFromKeyFile (XmlElement xml)
|
||||
{
|
||||
KeyFileData data;
|
||||
|
||||
data.licensee = getLicensee (xml);
|
||||
data.email = getEmail (xml);
|
||||
data.appID = getAppID (xml);
|
||||
data.machineNumbers.addArray (getMachineNumbers (xml));
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
const char* TracktionMarketplaceStatus::unlockedProp = "u";
|
||||
static const char* stateTagName = "REG";
|
||||
static const char* userNameProp = "user";
|
||||
static const char* passwordProp = "pw";
|
||||
|
||||
static var machineNumberAllowed (StringArray numbersFromKeyFile,
|
||||
StringArray localMachineNumbers)
|
||||
{
|
||||
var result;
|
||||
|
||||
for (int i = 0; i < localMachineNumbers.size(); ++i)
|
||||
{
|
||||
String localNumber (localMachineNumbers[i].trim());
|
||||
|
||||
if (localNumber.isNotEmpty())
|
||||
{
|
||||
for (int j = numbersFromKeyFile.size(); --j >= 0;)
|
||||
{
|
||||
var ok (localNumber.trim().equalsIgnoreCase (numbersFromKeyFile[j].trim()));
|
||||
result.swapWith (ok);
|
||||
|
||||
if (result)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
TracktionMarketplaceStatus::TracktionMarketplaceStatus() : status (stateTagName)
|
||||
{
|
||||
}
|
||||
|
||||
TracktionMarketplaceStatus::~TracktionMarketplaceStatus()
|
||||
{
|
||||
}
|
||||
|
||||
void TracktionMarketplaceStatus::load()
|
||||
{
|
||||
MemoryBlock mb;
|
||||
mb.fromBase64Encoding (getState());
|
||||
|
||||
if (mb.getSize() > 0)
|
||||
status = ValueTree::readFromGZIPData (mb.getData(), mb.getSize());
|
||||
else
|
||||
status = ValueTree (stateTagName);
|
||||
|
||||
if (machineNumberAllowed (StringArray ("1234"), getLocalMachineIDs()))
|
||||
status.removeProperty (unlockedProp, nullptr);
|
||||
}
|
||||
|
||||
void TracktionMarketplaceStatus::save()
|
||||
{
|
||||
MemoryOutputStream mo;
|
||||
|
||||
{
|
||||
GZIPCompressorOutputStream gzipStream (&mo, 9);
|
||||
status.writeToStream (gzipStream);
|
||||
}
|
||||
|
||||
saveState (mo.getMemoryBlock().toBase64Encoding());
|
||||
}
|
||||
|
||||
static String getEncodedIDString (const String& input)
|
||||
{
|
||||
static const char* const platform =
|
||||
#if JUCE_MAC
|
||||
"M";
|
||||
#elif JUCE_WINDOWS
|
||||
"W";
|
||||
#elif JUCE_LINUX
|
||||
"L";
|
||||
#elif JUCE_IOS
|
||||
"I";
|
||||
#elif JUCE_ANDROID
|
||||
"A";
|
||||
#endif
|
||||
|
||||
return platform + MD5 ((input + "salt_1" + platform).toUTF8())
|
||||
.toHexString().substring (0, 9).toUpperCase();
|
||||
}
|
||||
|
||||
StringArray TracktionMarketplaceStatus::getLocalMachineIDs()
|
||||
{
|
||||
StringArray nums;
|
||||
|
||||
// First choice for an ID number is a filesystem ID for the user's home
|
||||
// folder or windows directory.
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
uint64 num = File::getSpecialLocation (File::windowsSystemDirectory).getFileIdentifier();
|
||||
#else
|
||||
uint64 num = File ("~").getFileIdentifier();
|
||||
#endif
|
||||
|
||||
if (num != 0)
|
||||
{
|
||||
nums.add (getEncodedIDString (String::toHexString ((int64) num)));
|
||||
return nums;
|
||||
}
|
||||
|
||||
// ..if that fails, use the MAC addresses..
|
||||
|
||||
Array<MACAddress> addresses;
|
||||
MACAddress::findAllAddresses (addresses);
|
||||
|
||||
for (int i = 0; i < addresses.size(); ++i)
|
||||
nums.add (getEncodedIDString (addresses[i].toString()));
|
||||
|
||||
jassert (nums.size() > 0); // failed to create any IDs!
|
||||
return nums;
|
||||
}
|
||||
|
||||
URL TracktionMarketplaceStatus::getServerAuthenticationURL()
|
||||
{
|
||||
return URL ("https://www.tracktion.com/marketplace/authenticate.php");
|
||||
}
|
||||
|
||||
String TracktionMarketplaceStatus::getWebsiteName()
|
||||
{
|
||||
return "tracktion.com";
|
||||
}
|
||||
|
||||
void TracktionMarketplaceStatus::setUserEmail (const String& usernameOrEmail)
|
||||
{
|
||||
status.setProperty (userNameProp, usernameOrEmail, nullptr);
|
||||
}
|
||||
|
||||
String TracktionMarketplaceStatus::getUserEmail() const
|
||||
{
|
||||
return status[userNameProp].toString();
|
||||
}
|
||||
|
||||
bool TracktionMarketplaceStatus::applyKeyFile (String keyFileContent)
|
||||
{
|
||||
KeyFileUtils::KeyFileData data;
|
||||
data = KeyFileUtils::getDataFromKeyFile (KeyFileUtils::getXmlFromKeyFile (keyFileContent, getPublicKey()));
|
||||
|
||||
if (data.licensee.isNotEmpty() && data.email.isNotEmpty() && data.appID == getMarketplaceProductID())
|
||||
{
|
||||
setUserEmail (data.email);
|
||||
|
||||
if (! isUnlocked())
|
||||
{
|
||||
var actualResult (0), dummyResult (1.0);
|
||||
var v (machineNumberAllowed (data.machineNumbers, getLocalMachineIDs()));
|
||||
actualResult.swapWith (v);
|
||||
v = machineNumberAllowed (StringArray ("01"), getLocalMachineIDs());
|
||||
dummyResult.swapWith (v);
|
||||
jassert (! dummyResult);
|
||||
|
||||
if ((! dummyResult) && actualResult)
|
||||
status.setProperty (unlockedProp, actualResult, nullptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool canConnectToWebsite (const URL& url)
|
||||
{
|
||||
ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String(), 2000, nullptr));
|
||||
return in != nullptr;
|
||||
}
|
||||
|
||||
static bool areMajorWebsitesAvailable()
|
||||
{
|
||||
const char* urlsToTry[] = { "http://google.com", "http://bing.com", "http://amazon.com", nullptr};
|
||||
|
||||
for (const char** url = urlsToTry; *url != nullptr; ++url)
|
||||
if (canConnectToWebsite (URL (*url)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TracktionMarketplaceStatus::UnlockResult TracktionMarketplaceStatus::handleXmlReply (XmlElement xml)
|
||||
{
|
||||
UnlockResult r;
|
||||
|
||||
if (const XmlElement* keyNode = xml.getChildByName ("KEY"))
|
||||
{
|
||||
const String keyText (keyNode->getAllSubText().trim());
|
||||
r.succeeded = keyText.length() > 10 && applyKeyFile (keyText);
|
||||
}
|
||||
|
||||
if (xml.hasTagName ("MESSAGE"))
|
||||
r.informativeMessage = xml.getStringAttribute ("message").trim();
|
||||
|
||||
if (xml.hasTagName ("ERROR"))
|
||||
r.errorMessage = xml.getStringAttribute ("error").trim();
|
||||
|
||||
if (xml.getStringAttribute ("url").isNotEmpty())
|
||||
r.urlToLaunch = xml.getStringAttribute ("url").trim();
|
||||
|
||||
if (r.errorMessage.isEmpty() && r.informativeMessage.isEmpty() && r.urlToLaunch.isEmpty() && ! r.succeeded)
|
||||
r.errorMessage = TRANS ("Unexpected or corrupted reply from XYZ").replace ("XYZ", getWebsiteName()) + "...\n\n"
|
||||
+ TRANS("Please try again in a few minutes, and contact us for support if this message appears again.");
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
TracktionMarketplaceStatus::UnlockResult TracktionMarketplaceStatus::handleFailedConnection()
|
||||
{
|
||||
UnlockResult r;
|
||||
|
||||
r.errorMessage = TRANS("Couldn't connect to XYZ").replace ("XYZ", getWebsiteName()) + "...\n\n";
|
||||
|
||||
if (areMajorWebsitesAvailable())
|
||||
r.errorMessage << TRANS("Your internet connection seems to be OK, but our webserver "
|
||||
"didn't respond... This is most likely a temporary problem, so try "
|
||||
"again in a few minutes, but if it persists, please contact us for support!");
|
||||
else
|
||||
r.errorMessage << TRANS("No internet sites seem to be accessible from your computer.. Before trying again, "
|
||||
"please check that your network is working correctly, and make sure "
|
||||
"that any firewall/security software installed on your machine isn't "
|
||||
"blocking your web connection.");
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
TracktionMarketplaceStatus::UnlockResult TracktionMarketplaceStatus::attemptWebserverUnlock (const String& email,
|
||||
const String& password)
|
||||
{
|
||||
// This method will block while it contacts the server, so you must run it on a background thread!
|
||||
jassert (! MessageManager::getInstance()->isThisTheMessageThread());
|
||||
|
||||
URL url (getServerAuthenticationURL()
|
||||
.withParameter ("product", getMarketplaceProductID())
|
||||
.withParameter ("email", email)
|
||||
.withParameter ("pw", password)
|
||||
.withParameter ("os", SystemStats::getOperatingSystemName())
|
||||
.withParameter ("mach", getLocalMachineIDs()[0]));
|
||||
|
||||
DBG ("Trying to unlock via URL: " << url.toString (true));
|
||||
|
||||
const String reply (url.readEntireTextStream());
|
||||
|
||||
DBG ("Reply from server: " << reply);
|
||||
|
||||
ScopedPointer<XmlElement> xml (XmlDocument::parse (reply));
|
||||
|
||||
if (xml != nullptr)
|
||||
return handleXmlReply (*xml);
|
||||
|
||||
return handleFailedConnection();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String TracktionMarketplaceKeyGeneration::generateKeyFile (const String& appName,
|
||||
const String& userEmail,
|
||||
const String& userName,
|
||||
const String& machineNumbers,
|
||||
const RSAKey& privateKey)
|
||||
{
|
||||
XmlElement xml ("key");
|
||||
|
||||
xml.setAttribute ("user", userName);
|
||||
xml.setAttribute ("email", userEmail);
|
||||
xml.setAttribute ("mach", machineNumbers);
|
||||
xml.setAttribute ("app", appName);
|
||||
xml.setAttribute ("date", String::toHexString (Time::getCurrentTime().toMilliseconds()));
|
||||
|
||||
String comment;
|
||||
comment << "Keyfile for " << appName << newLine;
|
||||
|
||||
if (userName.isNotEmpty())
|
||||
comment << "User: " << userName << newLine;
|
||||
|
||||
comment << "Email: " << userEmail << newLine
|
||||
<< "Machine numbers: " << machineNumbers << newLine
|
||||
<< "Created: " << Time::getCurrentTime().toString (true, true);
|
||||
|
||||
return KeyFileUtils::createKeyFile (comment, xml, privateKey);
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
Contains information about whether your app has been unlocked for the current machine,
|
||||
and handles communication with the web-store to perform the unlock procedure.
|
||||
|
||||
To use this class, create a subclass of it, and implement its pure virtual methods
|
||||
(see their comments to find out what you'll need to make them do).
|
||||
|
||||
Then you can create an instance of your subclass which will hold the registration
|
||||
state. Typically, you'll want to just keep a single instance of the class around for
|
||||
the duration of your app. You can then call its methods to handle the various
|
||||
registration tasks.
|
||||
|
||||
Areas of your code that need to know whether the user is registered (e.g. to decide
|
||||
whether a particular feature is available) should call isUnlocked() to find out.
|
||||
|
||||
If you want to create a GUI that allows your users to enter their details and
|
||||
register, see the TracktionMarketplaceUnlockForm class.
|
||||
|
||||
@see TracktionMarketplaceUnlockForm, TracktionMarketplaceKeyGeneration
|
||||
*/
|
||||
class JUCE_API TracktionMarketplaceStatus
|
||||
{
|
||||
public:
|
||||
TracktionMarketplaceStatus();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~TracktionMarketplaceStatus();
|
||||
|
||||
//==============================================================================
|
||||
/** This must return your product's ID, as allocated by the store. */
|
||||
virtual String getMarketplaceProductID() = 0;
|
||||
|
||||
/** This must return the RSA public key for authenticating responses from
|
||||
the server for this app. You can get this key from your marketplace
|
||||
account page.
|
||||
*/
|
||||
virtual RSAKey getPublicKey() = 0;
|
||||
|
||||
/** This method must store the given string somewhere in your app's
|
||||
persistent properties, so it can be retrieved later by getState().
|
||||
*/
|
||||
virtual void saveState (const String&) = 0;
|
||||
|
||||
/** This method must retrieve the last state that was provided by the
|
||||
saveState method.
|
||||
|
||||
On first-run, it should just return an empty string.
|
||||
*/
|
||||
virtual String getState() = 0;
|
||||
|
||||
/** Returns a list of strings, any of which should be unique to this
|
||||
physical computer.
|
||||
|
||||
When testing whether the user is allowed to use the product on this
|
||||
machine, this list of tokens is compared to the ones that were stored
|
||||
on the Tracktion marketplace webserver.
|
||||
|
||||
The default implementation of this method will calculate some
|
||||
machine IDs based on things like network MAC addresses, hard-disk
|
||||
IDs, etc, but if you want, you can overload it to generate your
|
||||
own list of IDs.
|
||||
|
||||
The IDs that are returned should be short alphanumeric strings
|
||||
without any punctuation characters. Since users may need to type
|
||||
them, case is ignored when comparing them.
|
||||
|
||||
Note that the first item in the list is considered to be the
|
||||
"main" ID, and this will be the one that is displayed to the user
|
||||
and registered with the marketplace webserver. Subsequent IDs are
|
||||
just used as fallback to avoid false negatives when checking for
|
||||
registration on machines which have had hardware added/removed
|
||||
since the product was first registered.
|
||||
*/
|
||||
virtual StringArray getLocalMachineIDs();
|
||||
|
||||
/** Can be overridden if necessary, but by default returns the tracktion.com
|
||||
marketplace server.
|
||||
*/
|
||||
virtual URL getServerAuthenticationURL();
|
||||
|
||||
/** Can be overridden if necessary, but by default returns "tracktion.com". */
|
||||
virtual String getWebsiteName();
|
||||
|
||||
//==============================================================================
|
||||
// The following methods can be called by your app:
|
||||
|
||||
/** Returns true if the product has been successfully authorised for this machine.
|
||||
|
||||
The reason it returns a variant rather than a bool is just to make it marginally
|
||||
more tedious for crackers to work around. Hopefully if this method gets inlined
|
||||
they'll need to hack all the places where you call it, rather than just the
|
||||
function itself.
|
||||
|
||||
Bear in mind that each place where you check this return value will need to be
|
||||
changed by a cracker in order to unlock your app, so the more places you call this
|
||||
method, the more hassle it will be for them to find and crack them all.
|
||||
*/
|
||||
inline var isUnlocked() const { return status[unlockedProp]; }
|
||||
|
||||
/** Optionally allows the app to provide the user's email address if
|
||||
it is known.
|
||||
You don't need to call this, but if you do it may save the user
|
||||
typing it in.
|
||||
*/
|
||||
void setUserEmail (const String& usernameOrEmail);
|
||||
|
||||
/** Returns the user's email address if known. */
|
||||
String getUserEmail() const;
|
||||
|
||||
/** Attempts to perform an unlock using a block of key-file data provided.
|
||||
You may wish to use this as a way of allowing a user to unlock your app
|
||||
by drag-and-dropping a file containing the key data, or by letting them
|
||||
select such a file. This is often needed for allowing registration on
|
||||
machines without internet access.
|
||||
*/
|
||||
bool applyKeyFile (String keyFileContent);
|
||||
|
||||
/** This provides some details about the reply that the server gave in a call
|
||||
to attemptWebserverUnlock().
|
||||
*/
|
||||
struct UnlockResult
|
||||
{
|
||||
/** If an unlock operation fails, this is the error message that the webserver
|
||||
supplied (or a message saying that the server couldn't be contacted)
|
||||
*/
|
||||
String errorMessage;
|
||||
|
||||
/** This is a message that the webserver returned, and which the user should
|
||||
be shown.
|
||||
|
||||
It's not necessarily an error message, e.g. it might say that there's a
|
||||
new version of the app available or some other status update.
|
||||
*/
|
||||
String informativeMessage;
|
||||
|
||||
/** If the webserver wants the user to be directed to a web-page for further
|
||||
information, this is the URL that it would like them to go to.
|
||||
*/
|
||||
String urlToLaunch;
|
||||
|
||||
/** If the unlock operation succeeded, this will be set to true. */
|
||||
bool succeeded;
|
||||
};
|
||||
|
||||
/** Contacts the webserver and attempts to perform a registration with the
|
||||
given user details.
|
||||
|
||||
The return value will either be a success, or a failure with an error message
|
||||
from the server, so you should show this message to your user.
|
||||
|
||||
Note that this is designed for the Tracktion marketplace server - if you're
|
||||
building your own server, this function probably won't do the right thing for you.
|
||||
*/
|
||||
UnlockResult attemptWebserverUnlock (const String& email, const String& password);
|
||||
|
||||
/** Attempts to load the status from the state retrieved by getState().
|
||||
Call this somewhere in your app's startup code.
|
||||
*/
|
||||
void load();
|
||||
|
||||
/** Triggers a call to saveState which you can use to store the current unlock status
|
||||
in your app's settings.
|
||||
*/
|
||||
void save();
|
||||
|
||||
private:
|
||||
ValueTree status;
|
||||
|
||||
UnlockResult handleXmlReply (XmlElement);
|
||||
UnlockResult handleFailedConnection();
|
||||
|
||||
static const char* unlockedProp;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TracktionMarketplaceStatus)
|
||||
};
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
struct Spinner : public Component, private Timer
|
||||
{
|
||||
Spinner() { startTimer (1000 / 50); }
|
||||
void timerCallback() override { repaint(); }
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey, 0, 0, getWidth(), getHeight());
|
||||
}
|
||||
};
|
||||
|
||||
struct TracktionMarketplaceUnlockForm::OverlayComp : public Component,
|
||||
private Thread,
|
||||
private Timer
|
||||
{
|
||||
OverlayComp (TracktionMarketplaceUnlockForm& f) : Thread (String()), form (f)
|
||||
{
|
||||
email = form.emailBox.getText();
|
||||
password = form.passwordBox.getText();
|
||||
addAndMakeVisible (spinner);
|
||||
|
||||
startThread (4);
|
||||
}
|
||||
|
||||
~OverlayComp()
|
||||
{
|
||||
stopThread (10000);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (Colours::white.withAlpha (0.97f));
|
||||
|
||||
g.setColour (Colours::black);
|
||||
g.setFont (15.0f);
|
||||
|
||||
g.drawFittedText (TRANS("Contacting XYZ...").replace ("XYZ", form.status.getWebsiteName()),
|
||||
getLocalBounds().reduced (20, 0).removeFromTop (proportionOfHeight (0.6f)),
|
||||
Justification::centred, 5);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
const int spinnerSize = 40;
|
||||
spinner.setBounds ((getWidth() - spinnerSize) / 2, proportionOfHeight (0.6f), spinnerSize, spinnerSize);
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
result = form.status.attemptWebserverUnlock (email, password);
|
||||
startTimer (100);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
spinner.setVisible (false);
|
||||
stopTimer();
|
||||
|
||||
if (result.errorMessage.isNotEmpty())
|
||||
{
|
||||
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
|
||||
TRANS("Registration Failed"),
|
||||
result.errorMessage);
|
||||
}
|
||||
else if (result.informativeMessage.isNotEmpty())
|
||||
{
|
||||
AlertWindow::showMessageBox (AlertWindow::InfoIcon,
|
||||
TRANS("Registration Complete!"),
|
||||
result.informativeMessage);
|
||||
}
|
||||
else if (result.urlToLaunch.isNotEmpty())
|
||||
{
|
||||
URL url (result.urlToLaunch);
|
||||
url.launchInDefaultBrowser();
|
||||
}
|
||||
|
||||
if (result.succeeded)
|
||||
form.cancel();
|
||||
else
|
||||
delete this;
|
||||
}
|
||||
|
||||
TracktionMarketplaceUnlockForm& form;
|
||||
Spinner spinner;
|
||||
TracktionMarketplaceStatus::UnlockResult result;
|
||||
String email, password;
|
||||
};
|
||||
|
||||
static juce_wchar getDefaultPasswordChar() noexcept
|
||||
{
|
||||
#if JUCE_LINUX
|
||||
return 0x2022;
|
||||
#else
|
||||
return 0x25cf;
|
||||
#endif
|
||||
}
|
||||
|
||||
TracktionMarketplaceUnlockForm::TracktionMarketplaceUnlockForm (TracktionMarketplaceStatus& s,
|
||||
const String& userInstructions)
|
||||
: status (s),
|
||||
message (String(), userInstructions),
|
||||
passwordBox (String(), getDefaultPasswordChar()),
|
||||
registerButton (TRANS("Register")),
|
||||
cancelButton (TRANS ("Cancel"))
|
||||
{
|
||||
// Please supply a message to tell your users what to do!
|
||||
jassert (userInstructions.isNotEmpty());
|
||||
|
||||
setOpaque (true);
|
||||
|
||||
emailBox.setText (status.getUserEmail());
|
||||
message.setJustificationType (Justification::centred);
|
||||
|
||||
addAndMakeVisible (message);
|
||||
addAndMakeVisible (emailBox);
|
||||
addAndMakeVisible (passwordBox);
|
||||
addAndMakeVisible (registerButton);
|
||||
addAndMakeVisible (cancelButton);
|
||||
|
||||
registerButton.addListener (this);
|
||||
cancelButton.addListener (this);
|
||||
|
||||
lookAndFeelChanged();
|
||||
setSize (500, 250);
|
||||
}
|
||||
|
||||
TracktionMarketplaceUnlockForm::~TracktionMarketplaceUnlockForm()
|
||||
{
|
||||
unlockingOverlay.deleteAndZero();
|
||||
}
|
||||
|
||||
void TracktionMarketplaceUnlockForm::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (Colours::lightgrey);
|
||||
}
|
||||
|
||||
void TracktionMarketplaceUnlockForm::resized()
|
||||
{
|
||||
/* If you're writing a plugin, then DO NOT USE A POP-UP A DIALOG WINDOW!
|
||||
Plugins that create external windows are incredibly annoying for users, and
|
||||
cause all sorts of headaches for hosts. Don't be the person who writes that
|
||||
plugin that irritates everyone with a nagging dialog box every time they scan!
|
||||
*/
|
||||
jassert (JUCEApplicationBase::isStandaloneApp() || findParentComponentOfClass<DialogWindow>() == nullptr);
|
||||
|
||||
const int buttonHeight = 22;
|
||||
|
||||
Rectangle<int> r (getLocalBounds().reduced (10, 20));
|
||||
|
||||
Rectangle<int> buttonArea (r.removeFromBottom (buttonHeight));
|
||||
registerButton.changeWidthToFitText (buttonHeight);
|
||||
cancelButton.changeWidthToFitText (buttonHeight);
|
||||
const int gap = 20;
|
||||
buttonArea = buttonArea.withSizeKeepingCentre (registerButton.getWidth() + gap + cancelButton.getWidth(), buttonHeight);
|
||||
registerButton.setBounds (buttonArea.removeFromLeft (registerButton.getWidth()));
|
||||
buttonArea.removeFromLeft (gap);
|
||||
cancelButton.setBounds (buttonArea);
|
||||
|
||||
r.removeFromBottom (20);
|
||||
|
||||
const int boxHeight = 24;
|
||||
passwordBox.setBounds (r.removeFromBottom (boxHeight));
|
||||
passwordBox.setInputRestrictions (64);
|
||||
|
||||
r.removeFromBottom (30);
|
||||
emailBox.setBounds (r.removeFromBottom (boxHeight));
|
||||
passwordBox.setInputRestrictions (256);
|
||||
|
||||
r.removeFromBottom (30);
|
||||
|
||||
message.setBounds (r);
|
||||
|
||||
if (unlockingOverlay != nullptr)
|
||||
unlockingOverlay->setBounds (getLocalBounds());
|
||||
}
|
||||
|
||||
void TracktionMarketplaceUnlockForm::lookAndFeelChanged()
|
||||
{
|
||||
Colour labelCol (findColour (TextEditor::backgroundColourId).contrasting (0.5f));
|
||||
|
||||
emailBox.setTextToShowWhenEmpty (TRANS("Email Address"), labelCol);
|
||||
passwordBox.setTextToShowWhenEmpty (TRANS("Password"), labelCol);
|
||||
}
|
||||
|
||||
void TracktionMarketplaceUnlockForm::showBubbleMessage (const String& text, Component& target)
|
||||
{
|
||||
bubble = new BubbleMessageComponent (500);
|
||||
addChildComponent (bubble);
|
||||
|
||||
AttributedString attString;
|
||||
attString.append (text, Font (15.0f));
|
||||
|
||||
bubble->showAt (getLocalArea (&target, target.getLocalBounds()),
|
||||
attString, 500, // numMillisecondsBeforeRemoving
|
||||
true, // removeWhenMouseClicked
|
||||
false); // deleteSelfAfterUse
|
||||
}
|
||||
|
||||
void TracktionMarketplaceUnlockForm::buttonClicked (Button* b)
|
||||
{
|
||||
if (b == ®isterButton)
|
||||
attemptRegistration();
|
||||
else if (b == &cancelButton)
|
||||
cancel();
|
||||
}
|
||||
|
||||
void TracktionMarketplaceUnlockForm::attemptRegistration()
|
||||
{
|
||||
if (unlockingOverlay == nullptr)
|
||||
{
|
||||
if (emailBox.getText().trim().length() < 3)
|
||||
{
|
||||
showBubbleMessage (TRANS ("Please enter a valid email address!"), emailBox);
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordBox.getText().trim().length() < 3)
|
||||
{
|
||||
showBubbleMessage (TRANS ("Please enter a valid password!"), passwordBox);
|
||||
return;
|
||||
}
|
||||
|
||||
status.setUserEmail (emailBox.getText());
|
||||
|
||||
addAndMakeVisible (unlockingOverlay = new OverlayComp (*this));
|
||||
resized();
|
||||
unlockingOverlay->enterModalState();
|
||||
}
|
||||
}
|
||||
|
||||
void TracktionMarketplaceUnlockForm::cancel()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/** Acts as a GUI which asks the user for their details, and calls the approriate
|
||||
methods on your TracktionMarketplaceStatus object to attempt to register the app.
|
||||
|
||||
You should create one of these components and add it to your parent window,
|
||||
or use a DialogWindow to display it as a pop-up. But if you're writing a plugin,
|
||||
then DO NOT USE A DIALOG WINDOW! Add it as a child component of your plugin's editor
|
||||
component instead. Plugins that pop up external registration windows are incredibly
|
||||
annoying, and cause all sorts of headaches for hosts. Don't be the person who
|
||||
writes that plugin that irritates everyone with a dialog box every time they
|
||||
try to scan for new plugins!
|
||||
|
||||
Note that after adding it, you should put the component into a modal state,
|
||||
and it will automatically delete itself when it has completed.
|
||||
|
||||
Although it deletes itself, it's also OK to delete it manually yourself
|
||||
if you need to get rid of it sooner.
|
||||
|
||||
@see TracktionMarketplaceStatus
|
||||
*/
|
||||
class JUCE_API TracktionMarketplaceUnlockForm : public Component,
|
||||
private ButtonListener
|
||||
{
|
||||
public:
|
||||
/** Creates an unlock form that will work with the given status object.
|
||||
The userInstructions will be displayed above the email and password boxes.
|
||||
*/
|
||||
TracktionMarketplaceUnlockForm (TracktionMarketplaceStatus&,
|
||||
const String& userInstructions);
|
||||
|
||||
/** Destructor. */
|
||||
~TracktionMarketplaceUnlockForm();
|
||||
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
|
||||
private:
|
||||
TracktionMarketplaceStatus& status;
|
||||
|
||||
Label message;
|
||||
TextEditor emailBox, passwordBox;
|
||||
TextButton registerButton, cancelButton;
|
||||
ScopedPointer<BubbleMessageComponent> bubble;
|
||||
|
||||
struct OverlayComp;
|
||||
friend struct OverlayComp;
|
||||
Component::SafePointer<Component> unlockingOverlay;
|
||||
|
||||
void buttonClicked (Button*) override;
|
||||
void attemptRegistration();
|
||||
void cancel();
|
||||
void showBubbleMessage (const String&, Component&);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TracktionMarketplaceUnlockForm)
|
||||
};
|
||||
Reference in New Issue
Block a user