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

New license registration code #5937

Merged
merged 65 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
4d0dfe2
New license dialog
bastianallgeier Nov 7, 2023
c3e8f8e
Make hub address configurable
bastianallgeier Nov 7, 2023
d2f900d
New license class and first steps to integrate it
bastianallgeier Nov 8, 2023
19f0d5e
`LicenseStatus` enum
distantnative Nov 11, 2023
b7e947c
A few fixes after the new status enum
bastianallgeier Nov 13, 2023
66ba9ee
Small improvements and lab example
bastianallgeier Nov 13, 2023
b803ecb
New button group and lab example setup
bastianallgeier Nov 14, 2023
541e0ff
New LicenseStatus definitions and dialogs
bastianallgeier Nov 14, 2023
81d93c4
Fix cs issues
bastianallgeier Nov 14, 2023
dcfbf5e
Add first set of unit tests for the license class
bastianallgeier Nov 15, 2023
3779579
Licenses: change status wording
distantnative Nov 14, 2023
197a5f3
Fix date issue and color
bastianallgeier Nov 15, 2023
eb7af57
New license type enum and unit tests
bastianallgeier Nov 15, 2023
dfaed36
Add API license model and fix more unit tests
bastianallgeier Nov 15, 2023
9087314
Try to fix CS issues manually
bastianallgeier Nov 15, 2023
11f2159
Fix dialog label
bastianallgeier Nov 15, 2023
668f86f
Renamed methods and fixed license verification
bastianallgeier Nov 15, 2023
593e6db
New License::update method and better renewal logic
bastianallgeier Nov 15, 2023
eae88e9
New license dialog
bastianallgeier Nov 7, 2023
b6e2aa2
Make hub address configurable
bastianallgeier Nov 7, 2023
0ffc660
New license class and first steps to integrate it
bastianallgeier Nov 8, 2023
754ca64
`LicenseStatus` enum
distantnative Nov 11, 2023
788c8c5
A few fixes after the new status enum
bastianallgeier Nov 13, 2023
5af81c4
Small improvements and lab example
bastianallgeier Nov 13, 2023
eb8aec8
New button group and lab example setup
bastianallgeier Nov 14, 2023
098ef2f
New LicenseStatus definitions and dialogs
bastianallgeier Nov 14, 2023
7410b32
Fix cs issues
bastianallgeier Nov 14, 2023
49a56ac
Add first set of unit tests for the license class
bastianallgeier Nov 15, 2023
fd671af
Licenses: change status wording
distantnative Nov 14, 2023
84133ab
Fix date issue and color
bastianallgeier Nov 15, 2023
ee59b98
New license type enum and unit tests
bastianallgeier Nov 15, 2023
029aa87
Add API license model and fix more unit tests
bastianallgeier Nov 15, 2023
7bf39f8
Try to fix CS issues manually
bastianallgeier Nov 15, 2023
023fe20
Fix dialog label
bastianallgeier Nov 15, 2023
ce22b68
Renamed methods and fixed license verification
bastianallgeier Nov 15, 2023
22a195b
New License::update method and better renewal logic
bastianallgeier Nov 15, 2023
32d10e8
Change translation strings
bastianallgeier Nov 20, 2023
115caf5
Merge branch 'v4/enhancement/licenses' of https://github.com/getkirby…
bastianallgeier Nov 20, 2023
ce3ff18
Remove license type after introduction of global type string
bastianallgeier Nov 20, 2023
1a27e75
Merge branch 'v4/develop' into v4/enhancement/licenses
bastianallgeier Nov 21, 2023
60af17c
Remove unnecessary section from license dialog
bastianallgeier Nov 23, 2023
e6d9a97
Adjust upgrade flow
bastianallgeier Nov 23, 2023
2153815
Use error messages from the hub
bastianallgeier Nov 23, 2023
e0030ef
Merge branch 'v4/develop' into v4/enhancement/licenses
bastianallgeier Nov 23, 2023
c9cbb92
Fix cs
distantnative Nov 23, 2023
af72185
Fix js unit tests
distantnative Nov 23, 2023
6c9db1b
Change theme for `LicenseStatus::Legacy`
distantnative Nov 24, 2023
f98e03a
Add some more unit tests
distantnative Nov 24, 2023
a08a227
Merge branch 'v4/develop' into v4/enhancement/licenses
bastianallgeier Nov 24, 2023
6e4335b
New LicenseStatus::Demo
bastianallgeier Nov 24, 2023
ba36a46
Use normalize instead of sanitize
bastianallgeier Nov 25, 2023
588856c
Add more checks to isComplete
bastianallgeier Nov 25, 2023
abedd0e
Code style fix
bastianallgeier Nov 25, 2023
fa52a27
Remove outdated translation string
bastianallgeier Nov 25, 2023
3f812e4
Better code comment
bastianallgeier Nov 25, 2023
5a275da
Better translation consistency
bastianallgeier Nov 25, 2023
1acfe09
Translate demo state
bastianallgeier Nov 25, 2023
5e62e65
Fix unit test
bastianallgeier Nov 25, 2023
68cfc8d
Merge branch 'v4/develop' into v4/enhancement/licenses
bastianallgeier Nov 25, 2023
22fb0aa
Fix unit tests
bastianallgeier Nov 25, 2023
8b24ce7
Fix unit test class position
bastianallgeier Nov 25, 2023
4d00ab5
Fix coding style
lukasbestle Nov 25, 2023
1e9f119
Merge branch 'v4/develop' into v4/enhancement/licenses
bastianallgeier Nov 27, 2023
0041eba
Ignore parts that communicate with the hub
bastianallgeier Nov 27, 2023
8d41b8e
Fix cs issues
bastianallgeier Nov 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Renamed methods and fixed license verification
  • Loading branch information
bastianallgeier authored and distantnative committed Nov 20, 2023
commit ce22b68d2def103a3b8b015032f0650dd62f7465
4 changes: 1 addition & 3 deletions config/areas/system/dialogs.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@
];
},
'submit' => function () {
return [
'redirect' => App::instance()->system()->license()->checkout()
];
return App::instance()->system()->license()->renew();
}
],
// license registration
Expand Down
196 changes: 115 additions & 81 deletions src/Cms/License.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

use IntlDateFormatter;
use Kirby\Data\Json;
use Kirby\Exception\Exception;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Exception\LogicException;
use Kirby\Filesystem\F;
use Kirby\Http\Remote;
use Kirby\Toolkit\I18n;
use Kirby\Toolkit\Str;
use Kirby\Toolkit\V;
use Throwable;
Expand All @@ -33,12 +35,12 @@ class License
protected LicenseType $type;

public function __construct(
protected string|null $activated = null,
protected string|null $activation = null,
protected string|null $code = null,
protected string|null $domain = null,
protected string|null $email = null,
protected string|null $order = null,
protected string|null $purchased = null,
protected string|null $date = null,
protected string|null $signature = null,
) {
// sanitize the email address
bastianallgeier marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -48,17 +50,9 @@ public function __construct(
/**
* Returns the activation date if available
*/
public function activated(string|IntlDateFormatter|null $format = null): int|string|null
public function activation(string|IntlDateFormatter|null $format = null): int|string|null
{
return $this->activated !== null ? Str::date(strtotime($this->activated), $format) : null;
}

/**
* Creates a checkout link
*/
public function checkout(): string
{
return static::hub() . '/renew/' . hash('sha256', $this->code() . static::SALT);
return $this->activation !== null ? Str::date(strtotime($this->activation), $format) : null;
}

/**
Expand All @@ -79,25 +73,34 @@ public function code(bool $obfuscated = false): string|null
public function content(): array
{
return [
'activated' => $this->activated,
'code' => $this->code,
'email' => $this->email,
'order' => $this->order,
'purchased' => $this->purchased,
'signature' => $this->signature,
'activation' => $this->activation,
'code' => $this->code,
'date' => $this->date,
'domain' => $this->domain,
'email' => $this->email,
'order' => $this->order,
'signature' => $this->signature,
];
}

/**
* Returns the activated domain if available
* Returns the purchase date if available
*/
public function date(string|IntlDateFormatter|null $format = null): int|string|null
{
return $this->date !== null ? Str::date(strtotime($this->date), $format) : null;
}

/**
* Returns the activation domain if available
*/
public function domain(): string|null
{
return $this->domain;
}

/**
* Returns the activated email if available
* Returns the activation email if available
*/
public function email(): string|null
{
Expand Down Expand Up @@ -128,7 +131,7 @@ public function isComplete(): bool
if (
$this->domain !== null &&
$this->order !== null &&
$this->purchased !== null &&
$this->date !== null &&
$this->signature !== null &&
$this->hasValidEmailAddress() === true &&
$this->type() !== LicenseType::Invalid
Expand Down Expand Up @@ -160,7 +163,7 @@ public function isLegacy(): bool
// without an activation date, the license
// renewal cannot be evaluated and the license
// has to be marked as expired
if ($this->activated === null) {
if ($this->activation === null) {
return true;
}

Expand Down Expand Up @@ -220,29 +223,8 @@ public function isSigned(): bool
// get the public key
$pubKey = F::read(App::instance()->root('kirby') . '/kirby.pub');

// build the license verification data
$data = [
'activated' => $this->activated,
'code' => $this->code,
'domain' => $this->domain,
'email' => hash('sha256', $this->email . static::SALT),
'order' => $this->order,
'purchased' => $this->purchased,
];

// legacy licenses need a different payload for their signature
if ($this->isLegacy() === true) {
$data = [
'license' => $data['code'],
'order' => $data['order'],
'email' => $data['email'],
'domain' => $data['domain'],
'date' => $data['purchased'],
];
}

// verify the license signature
$data = json_encode($data);
$data = json_encode($this->signatureData());
$signature = hex2bin($this->signature);

if (openssl_verify($data, $signature, $pubKey, 'RSA-SHA256') !== 1) {
Expand Down Expand Up @@ -279,24 +261,16 @@ public function order(): string|null
public static function polyfill(array $license): array
{
return [
'activated' => $license['activated'] ?? null,
'code' => $license['code'] ?? $license['license'] ?? null,
'domain' => $license['domain'] ?? null,
'email' => $license['email'] ?? null,
'order' => $license['order'] ?? null,
'purchased' => $license['purchased'] ?? $license['date'] ?? null,
'signature' => $license['signature'] ?? null,
'activation' => $license['activation'] ?? null,
'code' => $license['code'] ?? $license['license'] ?? null,
'date' => $license['date'] ?? null,
'domain' => $license['domain'] ?? null,
'email' => $license['email'] ?? null,
'order' => $license['order'] ?? null,
'signature' => $license['signature'] ?? null,
];
}

/**
* Returns the purchase date if available
*/
public function purchased(string|IntlDateFormatter|null $format = null): int|string|null
{
return $this->purchased !== null ? Str::date(strtotime($this->purchased), $format) : null;
}

/**
* Reads the license file in the config folder
* and creates a new license instance for it.
Expand Down Expand Up @@ -329,50 +303,84 @@ public function register(): static
throw new InvalidArgumentException(['key' => 'license.domain']);
}

// @codeCoverageIgnoreStart
$response = Remote::get(static::hub() . '/register', [
'data' => [
'license' => $this->code,
'email' => $this->email,
'domain' => $this->domain
]
$response = $this->request('register', [
'license' => $this->code,
'email' => $this->email,
'domain' => $this->domain
]);

if ($response->code() !== 200) {
throw new LogicException($response->content());
}

// decode the response
$json = Json::decode($response->content());
$data = static::polyfill($json);
// @codeCoverageIgnoreEnd
$data = static::polyfill($response);

$this->activation = $data['activation'];
$this->code = $data['code'];
$this->date = $data['date'];
$this->order = $data['order'];
$this->signature = $data['signature'];

// clear the caches
unset($this->status, $this->type);

$this->activated = $data['activated'];
$this->code = $data['code'];
$this->email = $data['email'];
$this->order = $data['order'];
$this->purchased = $data['purchased'];
$this->signature = $data['signature'];

// save the new state of the license
$this->save();

return $this;
}

public function renew(): array
{
$response = $this->request('renew', [
'license' => $this->code,
'email' => $this->email,
'domain' => $this->domain
]);

// validate the redirect URL
if (empty($response['url']) === true || Str::startsWith($response['url'], static::hub()) === false) {
throw new Exception('We couldn’t redirect you to the Hub');
}

return [
'redirect' => $response['url']
];
}

/**
* Returns the renewal date
*/
public function renewal(string|IntlDateFormatter|null $format = null): int|string|null
{
if ($this->activated === null) {
if ($this->activation === null) {
return null;
}

$time = strtotime('+3 years', $this->activated());
$time = strtotime('+3 years', $this->activation());
bastianallgeier marked this conversation as resolved.
Show resolved Hide resolved
return Str::date($time, $format);
}

/**
* Sends a hub request
*/
public function request(string $path, array $data): array
{
// @codeCoverageIgnoreStart
$response = Remote::get(static::hub() . '/' . $path, [
'data' => $data
]);

// handle request errors
if ($response->code() !== 200) {
$error = $response->json()['message'] ?? null;
$message = I18n::translate($error, 'The request failed');

throw new LogicException($message, $response->code());
}

return $response->json();
// @codeCoverageIgnoreEnd
}

/**
* Prepares the domain to be comparable
*/
Expand Down Expand Up @@ -429,6 +437,32 @@ public function signature(): string|null
return $this->signature;
}

/**
* Creates the signature data array to compare
* with the signature in ::isSigned
*/
public function signatureData(): array
{
if ($this->type() === LicenseType::Legacy) {
return [
'license' => $this->code,
'order' => $this->order,
'email' => hash('sha256', $this->email . static::SALT),
'domain' => $this->domain,
'date' => $this->date,
];
}

return [
'activation' => $this->activation,
'code' => $this->code,
'date' => $this->date,
'domain' => $this->domain,
'email' => hash('sha256', $this->email . static::SALT),
'order' => $this->order,
];
}

/**
* Returns the license status as string
* This is used to build the proper UI elements
Expand Down
Loading