mirror of
https://github.com/typemill/typemill.git
synced 2025-07-30 19:00:32 +02:00
v2.2.2 finish and refactor license class
This commit is contained in:
@@ -55,8 +55,10 @@ class ControllerApiSystemExtensions extends Controller
|
||||
if(isset($definitions['license']) && in_array($definitions['license'], ['MAKER', 'BUSINESS']))
|
||||
{
|
||||
$license = new License();
|
||||
|
||||
# checks if license is valid and returns scope
|
||||
$licenseScope = $license->getLicenseScope($this->c->get('urlinfo'));
|
||||
|
||||
|
||||
if(!isset($licenseScope[$definitions['license']]))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
|
@@ -184,9 +184,12 @@ class ControllerWebSystem extends Controller
|
||||
$parser = $this->routeParser
|
||||
);
|
||||
|
||||
$message = false;
|
||||
$license = new License();
|
||||
$licensefields = $license->getLicenseFields();
|
||||
$licensedata = $license->getLicenseData($this->c->get('urlinfo'));
|
||||
$licensedata = $license->getLicenseFile();
|
||||
|
||||
# disable input fields if license data exist (readonly)
|
||||
if($licensedata)
|
||||
{
|
||||
foreach($licensefields as $key => $licensefield)
|
||||
@@ -195,14 +198,24 @@ class ControllerWebSystem extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
# check license data
|
||||
$licensecheck = $license->checkLicense($licensedata, $this->c->get('urlinfo'));
|
||||
if(!$licensecheck)
|
||||
{
|
||||
$message = $license->getMessage();
|
||||
}
|
||||
|
||||
unset($licensedata['signature']);
|
||||
|
||||
return $this->c->get('view')->render($response, 'system/license.twig', [
|
||||
'settings' => $this->settings,
|
||||
'darkmode' => $request->getAttribute('c_darkmode'),
|
||||
'mainnavi' => $mainNavigation,
|
||||
'jsdata' => [
|
||||
'systemnavi' => $systemNavigation,
|
||||
'systemnavi' => $systemNavigation,
|
||||
'licensedata' => $licensedata,
|
||||
'licensefields' => $licensefields,
|
||||
'message' => $message,
|
||||
'labels' => $this->c->get('translations'),
|
||||
'urlinfo' => $this->c->get('urlinfo') ]
|
||||
]);
|
||||
|
@@ -7,7 +7,7 @@ use Typemill\Static\Translations;
|
||||
|
||||
class License
|
||||
{
|
||||
public $message = '';
|
||||
private $message = '';
|
||||
|
||||
private $plans = [
|
||||
'MAKER' => [
|
||||
@@ -25,59 +25,22 @@ class License
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
# used for license management in admin settings
|
||||
public function getLicenseData(array $urlinfo)
|
||||
public function getLicenseFile()
|
||||
{
|
||||
# returns data for settings page
|
||||
$licensedata = $this->checkLicense();
|
||||
if($licensedata)
|
||||
{
|
||||
$licensedata['plan'] = $this->plans[$licensedata['plan']]['name'];
|
||||
$licensedata['domaincheck'] = $this->checkLicenseDomain($licensedata['domain'], $urlinfo);
|
||||
$licensedata['datecheck'] = $this->checkLicenseDate($licensedata['payed_until']);
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
|
||||
return $licensedata;
|
||||
$licensefile = $storage->getYaml('basepath', 'settings', 'license.yaml');
|
||||
|
||||
if($licensefile)
|
||||
{
|
||||
return $licensefile;
|
||||
}
|
||||
|
||||
$this->message = 'Error loading license: ' . $storage->getError();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# used to activate or deactivate features that require a license
|
||||
public function getLicenseScope(array $urlinfo)
|
||||
{
|
||||
$licensedata = $this->checkLicense();
|
||||
|
||||
if(!$licensedata)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$domain = $this->checkLicenseDomain($licensedata['domain'], $urlinfo);
|
||||
$date = $this->checkLicenseDate($licensedata['payed_until']);
|
||||
|
||||
if($domain && $date)
|
||||
{
|
||||
return $this->plans[$licensedata['plan']]['scope'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function refreshLicense()
|
||||
{
|
||||
# same as update
|
||||
}
|
||||
|
||||
private function updateLicence()
|
||||
{
|
||||
# if license not valid anymore, check server for update
|
||||
}
|
||||
|
||||
private function controlLicence()
|
||||
{
|
||||
# regularly check license on server each month.
|
||||
}
|
||||
|
||||
public function getLicenseFields()
|
||||
{
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
@@ -86,68 +49,152 @@ class License
|
||||
return $licensefields;
|
||||
}
|
||||
|
||||
# check the local licence file (like pem or pub)
|
||||
private function checkLicense()
|
||||
# used to activate or deactivate features that require a license
|
||||
public function getLicenseScope(array $urlinfo)
|
||||
{
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
|
||||
$licensedata = $storage->getYaml('basepath', 'settings', 'license.yaml');
|
||||
|
||||
$licensedata = $this->getLicenseFile();
|
||||
if(!$licensedata)
|
||||
{
|
||||
$this->message = Translations::translate('no license found');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isset($licensedata['license'],$licensedata['email'],$licensedata['domain'],$licensedata['plan'],$licensedata['payed_until'],$licensedata['signature']))
|
||||
# this means that a check (and update or call to cache timer) will take place when visit system license
|
||||
$licensecheck = $this->checkLicense($licensedata,$urlinfo);
|
||||
if(!$licensecheck)
|
||||
{
|
||||
$this->message = Translations::translate('License data incomplete');
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->plans[$licensedata['plan']]['scope'];
|
||||
}
|
||||
|
||||
# check the local licence file (like pem or pub)
|
||||
public function checkLicense($licensedata, array $urlinfo)
|
||||
{
|
||||
if(!isset(
|
||||
$licensedata['license'],
|
||||
$licensedata['email'],
|
||||
$licensedata['domain'],
|
||||
$licensedata['plan'],
|
||||
$licensedata['payed_until'],
|
||||
$licensedata['signature']
|
||||
))
|
||||
{
|
||||
$this->message = Translations::translate('License data are incomplete');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# check if license data are valid and not manipulated
|
||||
$licenseStatus = $this->validateLicense($licensedata);
|
||||
|
||||
if($licenseStatus !== true)
|
||||
{
|
||||
$this->message = Translations::translate('Validation failed') . ': ' . $licenseStatus;
|
||||
$this->message = Translations::translate('The license data are invalid.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# check here if payed until is in past
|
||||
$nextBillDate = new \DateTime($licensedata['payed_until']);
|
||||
$currentDate = new \DateTime();
|
||||
# check if website uses licensed domain
|
||||
$licenseDomain = $this->checkLicenseDomain($licensedata['domain'], $urlinfo);
|
||||
|
||||
if($nextBillDate < $currentDate)
|
||||
if(!$licenseDomain)
|
||||
{
|
||||
$this->message = Translations::translate('The website is running not under the domain of your license.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# check if subscription period is paid
|
||||
$subscriptionPaid = $this->checkLicenseDate($licensedata['payed_until']);
|
||||
|
||||
if(!$subscriptionPaid)
|
||||
{
|
||||
$this->message = Translations::translate('The subscription period is not paid yet.');
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
if(!$storage->timeoutIsOver('licenseupdate', 3600))
|
||||
{
|
||||
$this->message = Translations::translate('The subscription period has not been paid yet. We will check it every 60 minutes.') . $this->message;
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
$update = $this->updateLicense($licensedata);
|
||||
if(!$update)
|
||||
{
|
||||
$this->message = Translations::translate('We tried to check your subscription payment but it failed. Answer from server: ') . $this->message;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unset($licensedata['signature']);
|
||||
return true;
|
||||
}
|
||||
|
||||
return $licensedata;
|
||||
private function checkLicenseDate(string $payed_until)
|
||||
{
|
||||
# check here if payed until is in past
|
||||
$nextBillDate = new \DateTime($payed_until);
|
||||
$currentDate = new \DateTime();
|
||||
|
||||
if($nextBillDate > $currentDate)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function checkLicenseDomain(string $licensedomain, array $urlinfo)
|
||||
{
|
||||
$licensehost = parse_url($licensedomain, PHP_URL_HOST);
|
||||
$licensehost = str_replace("www.", "", $licensehost);
|
||||
|
||||
$thishost = parse_url($urlinfo['baseurl'], PHP_URL_HOST);
|
||||
$thishost = str_replace("www.", "", $thishost);
|
||||
|
||||
$whitelist = ['localhost', '127.0.0.1', 'typemilltest.', $licensehost];
|
||||
|
||||
foreach($whitelist as $domain)
|
||||
{
|
||||
if(substr($thishost, 0, strlen($domain)) == $domain)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function validateLicense($data)
|
||||
{
|
||||
$public_key_pem = $this->getPublicKeyPem();
|
||||
|
||||
$binary_signature = base64_decode($data['signature']);
|
||||
|
||||
$data['email'] = $this->hashMail($data['email']);
|
||||
unset($data['signature']);
|
||||
$licensedata = [
|
||||
'email' => $this->hashMail($data['email']),
|
||||
'domain' => $data['domain'],
|
||||
'license' => $data['license'],
|
||||
'plan' => $data['plan'],
|
||||
'payed_until' => $data['payed_until']
|
||||
];
|
||||
|
||||
ksort($licensedata);
|
||||
|
||||
# test manipulate data
|
||||
#$data['plan'] = 'wrong';
|
||||
|
||||
$data = json_encode($data);
|
||||
# $licensedata['plan'] = 'wrong';
|
||||
|
||||
$licensedata = json_encode($licensedata);
|
||||
|
||||
# Check signature
|
||||
$verified = openssl_verify($data, $binary_signature, $public_key_pem, OPENSSL_ALGO_SHA256);
|
||||
$public_key_pem = $this->getPublicKeyPem();
|
||||
|
||||
if(!$public_key_pem)
|
||||
{
|
||||
$this->message = Translations::translate('We could not find or read the public_key.pem in the settings-folder.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$binary_signature = base64_decode($data['signature']);
|
||||
|
||||
$verified = openssl_verify($licensedata, $binary_signature, $public_key_pem, OPENSSL_ALGO_SHA256);
|
||||
|
||||
if ($verified == 1)
|
||||
{
|
||||
@@ -155,13 +202,13 @@ class License
|
||||
}
|
||||
elseif ($verified == 0)
|
||||
{
|
||||
$this->message = 'License data are invalid';
|
||||
$this->message = Translations::translate('License validation failed');
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->message = Translations::translate('There was an error checking the license signature');
|
||||
$this->message = Translations::translate('There was an error checking the license signature');
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -170,57 +217,57 @@ class License
|
||||
public function activateLicense($params)
|
||||
{
|
||||
# prepare data for call to licence server
|
||||
|
||||
$readableMail = trim($params['email']);
|
||||
|
||||
$licensedata = [
|
||||
'license' => $params['license'],
|
||||
'email' => $this->hashMail($params['email']),
|
||||
'email' => $this->hashMail($readableMail),
|
||||
'domain' => $params['domain']
|
||||
];
|
||||
|
||||
$postdata = http_build_query($licensedata);
|
||||
$postdata = http_build_query($licensedata);
|
||||
|
||||
$authstring = $this->getPublicKeyPem();
|
||||
$authstring = hash('sha256', substr($authstring, 0, 50));
|
||||
$authstring = $this->getPublicKeyPem();
|
||||
$authstring = hash('sha256', substr($authstring, 0, 50));
|
||||
|
||||
$options = array (
|
||||
'http' => array (
|
||||
'method' => 'POST',
|
||||
'method' => 'POST',
|
||||
'ignore_errors' => true,
|
||||
'header' => "Content-Type: application/x-www-form-urlencoded\r\n" .
|
||||
"Accept: application/json\r\n" .
|
||||
"Authorization: $authstring\r\n" .
|
||||
"Connection: close\r\n",
|
||||
'content' => $postdata
|
||||
'header' => "Content-Type: application/x-www-form-urlencoded\r\n" .
|
||||
"Accept: application/json\r\n" .
|
||||
"Authorization: $authstring\r\n" .
|
||||
"Connection: close\r\n",
|
||||
'content' => $postdata
|
||||
)
|
||||
);
|
||||
|
||||
$context = stream_context_create($options);
|
||||
$context = stream_context_create($options);
|
||||
|
||||
$response = file_get_contents('https://service.typemill.net/api/v1/activate', false, $context);
|
||||
$response = file_get_contents('https://service.typemill.net/api/v1/activate', false, $context);
|
||||
|
||||
$signedLicense = json_decode($response,true);
|
||||
|
||||
if(substr($http_response_header[0], -6) != "200 OK")
|
||||
{
|
||||
$this->message = Translations::translate('the license server responded with: ') . $http_response_header[0];
|
||||
$message = $http_response_header[0];
|
||||
if(isset($signedLicense['code']) && ($signedLicense['code'] != ''))
|
||||
{
|
||||
$message = $signedLicense['code'];
|
||||
}
|
||||
$this->message = Translations::translate('the license server responded with: ') . $message;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$signedLicense = json_decode($response,true);
|
||||
|
||||
if(isset($signedLicense['code']))
|
||||
{
|
||||
# $this->message = 'Something went wrong. Please check your input data or contact the support.';
|
||||
$this->message = $signedLicense['code'];
|
||||
$this->message = $signedLicense['code'];
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
# check for positive and validate response data
|
||||
if($signedLicense['license'])
|
||||
{
|
||||
$this->message = ;
|
||||
}
|
||||
*/
|
||||
$signedLicense['license']['email'] = trim($params['email']);
|
||||
$signedLicense['license']['email'] = $readableMail;
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
|
||||
$result = $storage->updateYaml('settingsFolder', '', 'license.yaml', $signedLicense['license']);
|
||||
@@ -234,34 +281,75 @@ class License
|
||||
return true;
|
||||
}
|
||||
|
||||
private function checkLicenseDomain(string $licensedomain, array $urlinfo)
|
||||
# if license not valid anymore, check server for update
|
||||
private function updateLicense($data)
|
||||
{
|
||||
$licensehost = parse_url($licensedomain, PHP_URL_HOST);
|
||||
$licensehost = str_replace("www.", "", $licensehost);
|
||||
$readableMail = trim($data['email']);
|
||||
|
||||
$thishost = parse_url($urlinfo['baseurl'], PHP_URL_HOST);
|
||||
$thishost = str_replace("www.", "", $thishost);
|
||||
$licensedata = [
|
||||
'license' => $data['license'],
|
||||
'email' => $this->hashMail($readableMail),
|
||||
'domain' => $data['domain'],
|
||||
'signature' => $data['signature']
|
||||
];
|
||||
|
||||
$whitelist = ['localhost', '127.0.0.1', 'typemilltest.', $licensehost];
|
||||
$postdata = http_build_query($licensedata);
|
||||
|
||||
foreach($whitelist as $domain)
|
||||
$authstring = $this->getPublicKeyPem();
|
||||
$authstring = hash('sha256', substr($authstring, 0, 50));
|
||||
|
||||
$options = array (
|
||||
'http' => array (
|
||||
'method' => 'POST',
|
||||
'ignore_errors' => true,
|
||||
'header' => "Content-Type: application/x-www-form-urlencoded\r\n" .
|
||||
"Accept: application/json\r\n" .
|
||||
"Authorization: $authstring\r\n" .
|
||||
"Connection: close\r\n",
|
||||
'content' => $postdata
|
||||
)
|
||||
);
|
||||
|
||||
$context = stream_context_create($options);
|
||||
|
||||
$response = file_get_contents('https://service.typemill.net/api/v1/update', false, $context);
|
||||
|
||||
$signedLicense = json_decode($response,true);
|
||||
|
||||
if(substr($http_response_header[0], -6) != "200 OK")
|
||||
{
|
||||
if(substr($thishost, 0, strlen($domain)) == $domain)
|
||||
$message = $http_response_header[0];
|
||||
|
||||
if(isset($signedLicense['code']) && ($signedLicense['code'] != ''))
|
||||
{
|
||||
return true;
|
||||
$message = $signedLicense['code'];
|
||||
}
|
||||
|
||||
$this->message = Translations::translate('the license server responded with: ') . $message;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function checkLicenseDate(string $payed_until)
|
||||
{
|
||||
if(strtotime($payed_until) > strtotime(date('Y-m-d')))
|
||||
if(isset($signedLicense['code']))
|
||||
{
|
||||
return true;
|
||||
$this->message = $signedLicense['code'];
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
$signedLicense['license']['email'] = $readableMail;
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
|
||||
$result = $storage->updateYaml('settingsFolder', '', 'license.yaml', $signedLicense['license']);
|
||||
|
||||
if(!$result)
|
||||
{
|
||||
$this->message = 'We could not store the updated license: ' . $storage->getError();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function hashMail(string $mail)
|
||||
@@ -286,5 +374,4 @@ class License
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@@ -486,6 +486,38 @@ class Storage
|
||||
return false;
|
||||
}
|
||||
|
||||
######################
|
||||
## Timeout ##
|
||||
######################
|
||||
|
||||
public function timeoutIsOver($name, $timespan)
|
||||
{
|
||||
$location = 'cacheFolder';
|
||||
$folder = '';
|
||||
$filename = 'timer.yaml';
|
||||
|
||||
// Get current timers from the YAML file, if it exists
|
||||
$timers = $this->getYaml($location, $folder, $filename) ?: [];
|
||||
|
||||
$currentTime = time();
|
||||
$timeThreshold = $currentTime - $timespan;
|
||||
|
||||
# Check if the name exists and if the timestamp is older than the current time minus the timespan
|
||||
if (!isset($timers[$name]) || !is_numeric($timers[$name]) || $timers[$name] <= $timeThreshold)
|
||||
{
|
||||
# If the name doesn't exist or the timestamp is older, update the timer
|
||||
$timers[$name] = $currentTime;
|
||||
|
||||
# Update the YAML file with the new or updated timer
|
||||
$this->updateYaml($location, $folder, $filename, $timers);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# If the name exists and the timestamp is not older, return false
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
##################
|
||||
## IMAGES ##
|
||||
|
@@ -86,14 +86,12 @@ class Plugins
|
||||
return $premiumList['className'];
|
||||
}
|
||||
|
||||
$licenseType = false;
|
||||
|
||||
if(method_exists($className, 'setPremiumLicense'))
|
||||
{
|
||||
$licenseType = $className::setPremiumLicense();
|
||||
return $className::setPremiumLicense();
|
||||
}
|
||||
|
||||
return $licenseType;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function checkRouteArray($routes,$route)
|
||||
|
@@ -1,9 +1,8 @@
|
||||
const app = Vue.createApp({
|
||||
template: `<Transition name="initial" appear>
|
||||
<div v-if="licenseData.license">
|
||||
<div v-if="licenseData">
|
||||
<div>
|
||||
<p v-if="!licenseData.datecheck" class="bg-rose-500 text-white p-2 text-center">Your license is out of date. Please check if the payments for your subscription were successfull.</p>
|
||||
<p v-else-if="!licenseData.domaincheck" class="bg-rose-500 text-white p-2 text-center">Your license is only valid for the domain listed in your license data below.</p>
|
||||
<p v-if="licensemessage" class="bg-rose-500 text-white p-2 text-center">{{ licensemessage }}</p>
|
||||
<p v-else>Congratulations! Your license is active and you can enjoy all features until you cancel your subscription. You can manage your subscription at <a class="text-teal-500" href="https://paddle.net/">paddle.net</a></p>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-between">
|
||||
@@ -38,8 +37,7 @@ const app = Vue.createApp({
|
||||
<p class="w-full border p-2 bg-stone-100">{{ licenseData.payed_until }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="py-2 text-lg">The subscription extends automatically for 12 month every time until you cancel your subscription.</p>
|
||||
<p class="py-2 text-lg">For testing, you can also use the domains 'localhost', '127.0.0.1', and the subdomain 'typemilltest.'.</p>
|
||||
<p class="py-2 text-lg">The subscription extends automatically for 12 month every time until you cancel your subscription. For testing, you can also use the domains 'localhost', '127.0.0.1', and the subdomain 'typemilltest.'.</p>
|
||||
</div>
|
||||
<form v-else class="inline-block w-full">
|
||||
|
||||
@@ -81,6 +79,8 @@ const app = Vue.createApp({
|
||||
return {
|
||||
licenseData: data.licensedata,
|
||||
formDefinitions: data.licensefields,
|
||||
licensemessage: data.message,
|
||||
licensefound: data.licensefound,
|
||||
formData: {},
|
||||
message: '',
|
||||
messageClass: '',
|
||||
|
@@ -135,10 +135,12 @@ $timer['container'] = microtime(true);
|
||||
$dispatcher = new EventDispatcher();
|
||||
|
||||
/****************************
|
||||
* Check Licence *
|
||||
* Check Licence *
|
||||
****************************/
|
||||
|
||||
$license = new License();
|
||||
|
||||
# checks if license is valid and returns scope
|
||||
$settings['license'] = $license->getLicenseScope($urlinfo);
|
||||
|
||||
/****************************
|
||||
|
Reference in New Issue
Block a user