1
0
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:
trendschau
2024-02-29 12:46:18 +01:00
parent d19fd8c072
commit 6bc85bd8da
10 changed files with 273 additions and 138 deletions

View File

@@ -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([

View File

@@ -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') ]
]);

View File

@@ -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;
}
}

View File

@@ -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 ##

View File

@@ -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)

View File

@@ -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: '',

View File

@@ -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);
/****************************