parent
5211c407ff
commit
8fe29c5b89
12 changed files with 320 additions and 60 deletions
|
@ -6,7 +6,8 @@ import signal
|
|||
|
||||
from flask import abort
|
||||
from endpoints.api import (ApiResource, nickname, resource, internal_only, show_if,
|
||||
require_fresh_login, request, validate_json_request, verify_not_prod)
|
||||
require_fresh_login, request, validate_json_request, verify_not_prod,
|
||||
InvalidRequest)
|
||||
|
||||
from endpoints.common import common_login
|
||||
from app import app, config_provider, superusers, OVERRIDE_CONFIG_DIRECTORY
|
||||
|
@ -18,6 +19,7 @@ from data.database import User
|
|||
from util.config.configutil import add_enterprise_config_defaults
|
||||
from util.config.database import sync_database_with_config
|
||||
from util.config.validator import validate_service_for_config, CONFIG_FILENAMES
|
||||
from util.config.provider.license import decode_license, LicenseError
|
||||
from data.runmigration import run_alembic_migration
|
||||
from data.users import get_federated_service_name, get_users_handler
|
||||
|
||||
|
@ -62,6 +64,12 @@ class SuperUserRegistryStatus(ApiResource):
|
|||
'status': 'missing-config-dir'
|
||||
}
|
||||
|
||||
# If there is no license file, we need to ask the user to upload it.
|
||||
if not config_provider.has_license_file():
|
||||
return {
|
||||
'status': 'upload-license'
|
||||
}
|
||||
|
||||
# If there is no config file, we need to setup the database.
|
||||
if not config_provider.config_exists():
|
||||
return {
|
||||
|
@ -244,6 +252,50 @@ class SuperUserConfig(ApiResource):
|
|||
abort(403)
|
||||
|
||||
|
||||
@resource('/v1/superuser/config/license')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
class SuperUserSetAndValidateLicense(ApiResource):
|
||||
""" Resource for setting and validating a license. """
|
||||
schemas = {
|
||||
'ValidateLicense': {
|
||||
'type': 'object',
|
||||
'description': 'Validates and sets a license',
|
||||
'required': [
|
||||
'license',
|
||||
],
|
||||
'properties': {
|
||||
'license': {
|
||||
'type': 'string'
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@nickname('suSetAndValidateLicense')
|
||||
@verify_not_prod
|
||||
@validate_json_request('ValidateLicense')
|
||||
def post(self):
|
||||
""" Validates the given license contents and then saves it to the config volume. """
|
||||
if config_provider.has_license_file():
|
||||
abort(403)
|
||||
|
||||
license_contents = request.get_json()['license']
|
||||
try:
|
||||
decoded_license = decode_license(license_contents)
|
||||
except LicenseError as le:
|
||||
raise InvalidRequest(le.message)
|
||||
|
||||
if decoded_license.is_expired:
|
||||
raise InvalidRequest('License has expired')
|
||||
|
||||
config_provider.save_license(license_contents)
|
||||
return {
|
||||
'decoded': decoded_license.subscription,
|
||||
'success': True
|
||||
}
|
||||
|
||||
|
||||
@resource('/v1/superuser/config/file/<filename>')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
|
|
56
static/css/pages/setup.css
Normal file
56
static/css/pages/setup.css
Normal file
|
@ -0,0 +1,56 @@
|
|||
.initial-setup-modal .upload-license textarea {
|
||||
border: 1px solid #eee !important;
|
||||
transition: all ease-in-out 200ms;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.initial-setup-modal .upload-license textarea {
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .upload-license .validate-message {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .upload-license .license-invalid h5 {
|
||||
font-size: 18px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.initial-setup-modal .upload-license .license-invalid h6 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .upload-license .license-invalid .fa {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .license-valid h5 {
|
||||
color: #2FC98E;
|
||||
font-size: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .license-valid .fa {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .license-valid table {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .license-valid table td {
|
||||
border: 0px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.initial-setup-modal .license-valid table td:first-child {
|
||||
font-weight: bold;
|
||||
max-width: 100px;
|
||||
padding-right: 20px;
|
||||
}
|
|
@ -37,6 +37,15 @@
|
|||
// The config.yaml exists but it is invalid.
|
||||
'INVALID_CONFIG': 'config-invalid',
|
||||
|
||||
// License is being uploaded.
|
||||
'UPLOAD_LICENSE': 'upload-license',
|
||||
|
||||
// License is being validated.
|
||||
'VALIDATING_LICENSE': 'upload-license-validating',
|
||||
|
||||
// License is validated.
|
||||
'VALIDATED_LICENSE': 'upload-license-validated',
|
||||
|
||||
// DB is being configured.
|
||||
'CONFIG_DB': 'config-db',
|
||||
|
||||
|
@ -95,7 +104,10 @@
|
|||
$scope.currentConfig = null;
|
||||
|
||||
$scope.currentState = {
|
||||
'hasDatabaseSSLCert': false
|
||||
'hasDatabaseSSLCert': false,
|
||||
'licenseContents': '',
|
||||
'licenseError': null,
|
||||
'licenseDecoded': null,
|
||||
};
|
||||
|
||||
$scope.$watch('currentStep', function(currentStep) {
|
||||
|
@ -121,6 +133,7 @@
|
|||
case $scope.States.CREATE_SUPERUSER:
|
||||
case $scope.States.DB_RESTARTING:
|
||||
case $scope.States.CONFIG_DB:
|
||||
case $scope.States.UPLOAD_LICENSE:
|
||||
case $scope.States.VALID_CONFIG:
|
||||
case $scope.States.READY:
|
||||
$('#setupModal').modal({
|
||||
|
@ -131,6 +144,27 @@
|
|||
}
|
||||
});
|
||||
|
||||
$scope.validateLicense = function() {
|
||||
$scope.currentStep = $scope.States.VALIDATING_LICENSE;
|
||||
|
||||
var data = {
|
||||
'license': $scope.currentState.licenseContents
|
||||
};
|
||||
|
||||
ApiService.suSetAndValidateLicense(data).then(function(resp) {
|
||||
$scope.currentStep = $scope.States.VALIDATED_LICENSE;
|
||||
|
||||
$scope.currentState.licenseError = null;
|
||||
$scope.currentState.licenseDecoded = resp['decoded'];
|
||||
}, function(resp) {
|
||||
$scope.currentStep = $scope.States.UPLOAD_LICENSE;
|
||||
|
||||
$scope.currentState.licenseError = ApiService.getErrorMessage(resp);
|
||||
$scope.currentState.licenseContents = '';
|
||||
$scope.currentState.licenseDecoded = null;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.restartContainer = function(state) {
|
||||
$scope.currentStep = state;
|
||||
ContainerService.restartContainer(function() {
|
||||
|
@ -166,6 +200,7 @@
|
|||
var States = $scope.States;
|
||||
|
||||
return [
|
||||
isStepFamily(step, States.UPLOAD_LICENSE),
|
||||
isStepFamily(step, States.CONFIG_DB),
|
||||
isStepFamily(step, States.DB_SETUP),
|
||||
isStep(step, States.DB_RESTARTING),
|
||||
|
@ -191,6 +226,10 @@
|
|||
return false;
|
||||
};
|
||||
|
||||
$scope.beginSetup = function() {
|
||||
$scope.currentStep = $scope.States.CONFIG_DB;
|
||||
};
|
||||
|
||||
$scope.showInvalidConfigDialog = function() {
|
||||
var message = "The <code>config.yaml</code> file found in <code>conf/stack</code> could not be parsed."
|
||||
var title = "Invalid configuration file";
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
<div class="cor-tab-panel" style="padding: 20px;">
|
||||
<div class="co-alert alert alert-info">
|
||||
<span class="cor-step-bar" progress="stepProgress">
|
||||
<span class="cor-step" title="Configure Database" text="1"></span>
|
||||
<span class="cor-step" title="Upload License" text="1"></span>
|
||||
<span class="cor-step" title="Configure Database" text="2"></span>
|
||||
<span class="cor-step" title="Setup Database" icon="database"></span>
|
||||
<span class="cor-step" title="Container Restart" icon="refresh"></span>
|
||||
<span class="cor-step" title="Create Superuser" text="2"></span>
|
||||
<span class="cor-step" title="Configure Registry" text="3"></span>
|
||||
<span class="cor-step" title="Validate Configuration" text="4"></span>
|
||||
<span class="cor-step" title="Create Superuser" text="3"></span>
|
||||
<span class="cor-step" title="Configure Registry" text="4"></span>
|
||||
<span class="cor-step" title="Validate Configuration" text="5"></span>
|
||||
<span class="cor-step" title="Container Restart" icon="refresh"></span>
|
||||
<span class="cor-step" title="Setup Complete" icon="check"></span>
|
||||
</span>
|
||||
|
@ -36,12 +37,13 @@
|
|||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
<span class="cor-step-bar" progress="stepProgress">
|
||||
<span class="cor-step" title="Configure Database" text="1"></span>
|
||||
<span class="cor-step" title="Upload License" text="1"></span>
|
||||
<span class="cor-step" title="Configure Database" text="2"></span>
|
||||
<span class="cor-step" title="Setup Database" icon="database"></span>
|
||||
<span class="cor-step" title="Container Restart" icon="refresh"></span>
|
||||
<span class="cor-step" title="Create Superuser" text="2"></span>
|
||||
<span class="cor-step" title="Configure Registry" text="3"></span>
|
||||
<span class="cor-step" title="Validate Configuration" text="4"></span>
|
||||
<span class="cor-step" title="Create Superuser" text="3"></span>
|
||||
<span class="cor-step" title="Configure Registry" text="4"></span>
|
||||
<span class="cor-step" title="Validate Configuration" text="5"></span>
|
||||
<span class="cor-step" title="Container Restart" icon="refresh"></span>
|
||||
<span class="cor-step" title="Setup Complete" icon="check"></span>
|
||||
</span>
|
||||
|
@ -128,6 +130,37 @@
|
|||
The container must be restarted to apply the configuration changes.
|
||||
</div>
|
||||
|
||||
<!-- Content: VALIDATED_LICENSE -->
|
||||
<div class="modal-body license-valid" style="padding: 20px;"
|
||||
ng-show="isStep(currentStep, States.VALIDATED_LICENSE)">
|
||||
<h5><i class="fa fa-check"></i> License Validated</h5>
|
||||
Your license has been validated and saved. Please press "Next" to continue setup of your Quay Enterprise installation.
|
||||
<table class="co-table">
|
||||
<tr><td>Product:</td><td>{{ currentState.licenseDecoded.publicProductName || currentState.licenseDecoded.productName }}</td></tr>
|
||||
<tr><td>Plan:</td><td>{{ currentState.licenseDecoded.publicPlanName || currentState.licenseDecoded.planName }}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Content: UPLOAD_LICENSE or VALIDATING_LICENSE -->
|
||||
<div class="modal-body upload-license" style="padding: 20px;"
|
||||
ng-show="isStep(currentStep, States.UPLOAD_LICENSE, States.VALIDATING_LICENSE)"
|
||||
ng-class="isStep(currentStep, States.VALIDATING_LICENSE) ? 'validating' : 'entering'">
|
||||
<h4>
|
||||
Quay Enterprise License
|
||||
</h4>
|
||||
<div>
|
||||
Please provide your Quay Enterprise License. It can be found under the "Raw Format" tab
|
||||
of your Quay Enterprise subscription in the <a href="https://account.tectonic.com" target="_blank">Tectonic Account</a>.
|
||||
</div>
|
||||
<textarea id="enterLicenseBox" ng-model="currentState.licenseContents" placeholder="Paste your raw license here, which should already be in base64 format: GtqMjMwNDgyM3Vq..."
|
||||
ng-readonly="isStep(currentStep, States.VALIDATING_LICENSE)"></textarea>
|
||||
<div class="license-invalid" ng-visible="isStep(currentStep, States.UPLOAD_LICENSE) && currentState.licenseError">
|
||||
<h5><i class="fa fa-times-circle"></i> Validation Failed</h5>
|
||||
<h6>{{ currentState.licenseError }}</h6>
|
||||
Please try copying your license from the Tectonic Account again.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content: DB_SETUP or DB_SETUP_ERROR -->
|
||||
<div class="modal-body" style="padding: 20px;"
|
||||
ng-show="isStep(currentStep, States.DB_SETUP, States.DB_SETUP_ERROR)">
|
||||
|
@ -226,6 +259,29 @@
|
|||
Database Validation Issue: {{ errors.DatabaseValidationError }}
|
||||
</div>
|
||||
|
||||
<!-- Footer: UPLOAD_LICENSE or VALIDATING_LICENSE -->
|
||||
<div class="modal-footer"
|
||||
ng-show="isStep(currentStep, States.UPLOAD_LICENSE, States.VALIDATING_LICENSE)">
|
||||
<div ng-show="isStep(currentStep, States.VALIDATING_LICENSE)">
|
||||
<span class="cor-loader-inline"></span>
|
||||
Validating License...
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" ng-click="validateLicense()"
|
||||
ng-disabled="!currentState.licenseContents"
|
||||
ng-show="isStep(currentStep, States.UPLOAD_LICENSE)">
|
||||
Validate License
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer: VALIDATED_LICENSE -->
|
||||
<div class="modal-footer"
|
||||
ng-show="isStep(currentStep, States.VALIDATED_LICENSE)">
|
||||
<button type="submit" class="btn btn-primary" ng-click="beginSetup()">
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer: CONFIG_DB or DB_ERROR -->
|
||||
<div class="modal-footer"
|
||||
ng-show="isStep(currentStep, States.CONFIG_DB, States.DB_ERROR)">
|
||||
|
|
|
@ -3740,13 +3740,20 @@ class TestSuperUserCreateInitialSuperUser(ApiTestCase):
|
|||
|
||||
class TestSuperUserConfig(ApiTestCase):
|
||||
def test_get_status_update_config(self):
|
||||
# With no config the status should be 'config-db'.
|
||||
# With no config the status should be 'upload-license'.
|
||||
json = self.getJsonResponse(SuperUserRegistryStatus)
|
||||
self.assertEquals('config-db', json['status'])
|
||||
self.assertEquals('upload-license', json['status'])
|
||||
|
||||
# And the config should 401.
|
||||
self.getResponse(SuperUserConfig, expected_code=401)
|
||||
|
||||
# Add a fake license file.
|
||||
config_provider.save_license('something')
|
||||
|
||||
# With no config but a license the status should be 'config-db'.
|
||||
json = self.getJsonResponse(SuperUserRegistryStatus)
|
||||
self.assertEquals('config-db', json['status'])
|
||||
|
||||
# Add some fake config.
|
||||
fake_config = {
|
||||
'AUTHENTICATION_TYPE': 'Database',
|
||||
|
|
|
@ -3,6 +3,7 @@ import unittest
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
import jwt
|
||||
import json
|
||||
|
||||
from Crypto.PublicKey import RSA
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
@ -22,10 +23,14 @@ class TestLicense(unittest.TestCase):
|
|||
return (public_key, private_key)
|
||||
|
||||
def create_license(self, license_data):
|
||||
jwt_data = {
|
||||
'license': json.dumps(license_data),
|
||||
}
|
||||
|
||||
(public_key, private_key) = self.keys()
|
||||
|
||||
# Encode the license with the JWT key.
|
||||
encoded = jwt.encode(license_data, private_key, algorithm='RS256')
|
||||
encoded = jwt.encode(jwt_data, private_key, algorithm='RS256')
|
||||
|
||||
# Decode it into a license object.
|
||||
return decode_license(encoded, public_key_instance=public_key)
|
||||
|
@ -53,7 +58,7 @@ class TestLicense(unittest.TestCase):
|
|||
if 'duration' in kwargs:
|
||||
sub['durationPeriod'] = kwargs['duration']
|
||||
|
||||
license_data['subscriptions'] = [sub]
|
||||
license_data['subscriptions'] = {'somesub': sub}
|
||||
|
||||
decoded_license = self.create_license(license_data)
|
||||
return decoded_license
|
||||
|
@ -83,15 +88,15 @@ class TestLicense(unittest.TestCase):
|
|||
self.assertTrue(license.is_expired)
|
||||
|
||||
def test_monthly_license_valid(self):
|
||||
license = self.get_license(timedelta(days=30), service_end=timedelta(days=10), duration='monthly')
|
||||
license = self.get_license(timedelta(days=30), service_end=timedelta(days=10), duration='months')
|
||||
self.assertFalse(license.is_expired)
|
||||
|
||||
def test_monthly_license_withingrace(self):
|
||||
license = self.get_license(timedelta(days=30), service_end=timedelta(days=-10), duration='monthly')
|
||||
license = self.get_license(timedelta(days=30), service_end=timedelta(days=-10), duration='months')
|
||||
self.assertFalse(license.is_expired)
|
||||
|
||||
def test_monthly_license_outsidegrace(self):
|
||||
license = self.get_license(timedelta(days=30), service_end=timedelta(days=-40), duration='monthly')
|
||||
license = self.get_license(timedelta(days=30), service_end=timedelta(days=-40), duration='months')
|
||||
self.assertTrue(license.is_expired)
|
||||
|
||||
def test_yearly_license_withingrace(self):
|
||||
|
|
|
@ -21,7 +21,7 @@ class TestSuperUserRegistryStatus(ApiTestCase):
|
|||
def test_registry_status(self):
|
||||
with ConfigForTesting():
|
||||
json = self.getJsonResponse(SuperUserRegistryStatus)
|
||||
self.assertEquals('config-db', json['status'])
|
||||
self.assertEquals('upload-license', json['status'])
|
||||
|
||||
|
||||
class TestSuperUserConfigFile(ApiTestCase):
|
||||
|
|
|
@ -75,8 +75,12 @@ class BaseProvider(object):
|
|||
""" Returns whether the file with the given name exists under the config override volume. """
|
||||
raise NotImplementedError
|
||||
|
||||
def get_volume_file(self, filename, mode='r'):
|
||||
""" Returns a Python file referring to the given name under the config override volumne. """
|
||||
def get_volume_file(self, filename):
|
||||
""" Returns a Python file referring to the given name under the config override volume. """
|
||||
raise NotImplementedError
|
||||
|
||||
def write_volume_file(self, filename, contents):
|
||||
""" Writes the given contents to the config override volumne, with the given filename. """
|
||||
raise NotImplementedError
|
||||
|
||||
def save_volume_file(self, filename, flask_file):
|
||||
|
@ -91,6 +95,14 @@ class BaseProvider(object):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_license_file(self):
|
||||
""" Returns the contents of the license file. """
|
||||
try:
|
||||
return self.get_volume_file(LICENSE_FILENAME)
|
||||
except IOError:
|
||||
msg = 'Could not open license file. Please make sure it is in your config volume.'
|
||||
raise LicenseError(msg)
|
||||
|
||||
def validate_license(self, config):
|
||||
""" Validates that the configuration matches the license file (if any). """
|
||||
if not config.get('SETUP_COMPLETE', False):
|
||||
|
@ -102,11 +114,10 @@ class BaseProvider(object):
|
|||
self.license = decode_license(license_file_contents)
|
||||
self.license.validate(config)
|
||||
|
||||
def _get_license_file(self):
|
||||
""" Returns the contents of the license file. """
|
||||
try:
|
||||
return self.get_volume_file(LICENSE_FILENAME)
|
||||
except IOError:
|
||||
msg = 'Could not open license file. Please make sure it is in your config volume.'
|
||||
raise LicenseError(msg)
|
||||
def save_license(self, license_file_contents):
|
||||
""" Saves the given contents as the license file. """
|
||||
self.write_volume_file(LICENSE_FILENAME, license_file_contents)
|
||||
|
||||
def has_license_file(self):
|
||||
""" Returns true if a license file was found in the config directory. """
|
||||
return self.volume_file_exists(LICENSE_FILENAME)
|
||||
|
|
|
@ -49,8 +49,15 @@ class FileConfigProvider(BaseProvider):
|
|||
def volume_file_exists(self, filename):
|
||||
return os.path.exists(os.path.join(self.config_volume, filename))
|
||||
|
||||
def get_volume_file(self, filename, mode='r'):
|
||||
return open(os.path.join(self.config_volume, filename), mode)
|
||||
def get_volume_file(self, filename):
|
||||
return open(os.path.join(self.config_volume, filename))
|
||||
|
||||
def write_volume_file(self, filename, contents):
|
||||
filepath = os.path.join(self.config_volume, filename)
|
||||
with open(filepath, mode='w') as f:
|
||||
f.write(contents)
|
||||
|
||||
return filepath
|
||||
|
||||
def save_volume_file(self, filename, flask_file):
|
||||
filepath = os.path.join(self.config_volume, filename)
|
||||
|
|
|
@ -47,6 +47,14 @@ class KubernetesConfigProvider(FileConfigProvider):
|
|||
self._update_secret_file(self.yaml_filename, get_yaml(config_obj))
|
||||
super(KubernetesConfigProvider, self).save_config(config_obj)
|
||||
|
||||
def write_volume_file(self, filename, contents):
|
||||
super(KubernetesConfigProvider, self).write_volume_file(filename, contents)
|
||||
|
||||
try:
|
||||
self._update_secret_file(filename, contents)
|
||||
except IOError as ioe:
|
||||
raise CannotWriteConfigException(str(ioe))
|
||||
|
||||
def save_volume_file(self, filename, flask_file):
|
||||
filepath = super(KubernetesConfigProvider, self).save_volume_file(filename, flask_file)
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ from cryptography.hazmat.backends import default_backend
|
|||
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
||||
|
||||
import jwt
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -44,6 +45,19 @@ class License(object):
|
|||
def __init__(self, decoded):
|
||||
self.decoded = decoded
|
||||
|
||||
@property
|
||||
def subscription(self):
|
||||
""" Returns the Quay Enterprise subscription, if any. """
|
||||
for sub in self.decoded.get('subscriptions', {}).values():
|
||||
if sub.get('productName') == LICENSE_PRODUCT_NAME:
|
||||
return sub
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_expired(self):
|
||||
return self._get_expired(datetime.now())
|
||||
|
||||
def validate(self, config):
|
||||
""" Validates the license and all its entitlements against the given config. """
|
||||
# Check that the license has not expired.
|
||||
|
@ -58,10 +72,6 @@ class License(object):
|
|||
max_regions)
|
||||
raise LicenseValidationError(msg)
|
||||
|
||||
@property
|
||||
def is_expired(self):
|
||||
return self._get_expired(datetime.now())
|
||||
|
||||
def _get_expired(self, compare_date):
|
||||
# Check if the license overall has expired.
|
||||
expiration_date = _get_date(self.decoded, 'expirationDate')
|
||||
|
@ -70,37 +80,37 @@ class License(object):
|
|||
return True
|
||||
|
||||
# Check for any QE subscriptions.
|
||||
for sub in self.decoded.get('subscriptions', []):
|
||||
if sub.get('productName') != LICENSE_PRODUCT_NAME:
|
||||
continue
|
||||
sub = self.subscription
|
||||
if sub is None:
|
||||
return True
|
||||
|
||||
# Check for a trial-only license.
|
||||
if sub.get('trialOnly', False):
|
||||
trial_end_date = _get_date(sub, 'trialEnd')
|
||||
logger.debug('Trial-only license expires on %s', trial_end_date)
|
||||
return trial_end_date <= (compare_date - TRIAL_GRACE_PERIOD)
|
||||
# Check for a trial-only license.
|
||||
if sub.get('trialOnly', False):
|
||||
trial_end_date = _get_date(sub, 'trialEnd')
|
||||
logger.debug('Trial-only license expires on %s', trial_end_date)
|
||||
return trial_end_date <= (compare_date - TRIAL_GRACE_PERIOD)
|
||||
|
||||
# Check for a normal license that is in trial.
|
||||
service_end_date = _get_date(sub, 'serviceEnd')
|
||||
if sub.get('inTrial', False):
|
||||
# If the subscription is in a trial, but not a trial only
|
||||
# subscription, give 7 days after trial end to update license
|
||||
# to one which has been paid (they've put in a credit card and it
|
||||
# might auto convert, so we could assume it will auto-renew)
|
||||
logger.debug('In-trial license expires on %s', service_end_date)
|
||||
return service_end_date <= (compare_date - TRIAL_GRACE_PERIOD)
|
||||
# Check for a normal license that is in trial.
|
||||
service_end_date = _get_date(sub, 'serviceEnd')
|
||||
if sub.get('inTrial', False):
|
||||
# If the subscription is in a trial, but not a trial only
|
||||
# subscription, give 7 days after trial end to update license
|
||||
# to one which has been paid (they've put in a credit card and it
|
||||
# might auto convert, so we could assume it will auto-renew)
|
||||
logger.debug('In-trial license expires on %s', service_end_date)
|
||||
return service_end_date <= (compare_date - TRIAL_GRACE_PERIOD)
|
||||
|
||||
# Otherwise, check the service expiration.
|
||||
duration_period = sub.get('durationPeriod', 'monthly')
|
||||
# Otherwise, check the service expiration.
|
||||
duration_period = sub.get('durationPeriod', 'months')
|
||||
|
||||
# If the subscription is monthly, give 3 months grace period
|
||||
if duration_period == "monthly":
|
||||
logger.debug('Monthly license expires on %s', service_end_date)
|
||||
return service_end_date <= (compare_date - MONTHLY_GRACE_PERIOD)
|
||||
# If the subscription is monthly, give 3 months grace period
|
||||
if duration_period == "months":
|
||||
logger.debug('Monthly license expires on %s', service_end_date)
|
||||
return service_end_date <= (compare_date - MONTHLY_GRACE_PERIOD)
|
||||
|
||||
if duration_period == "years":
|
||||
logger.debug('Yearly license expires on %s', service_end_date)
|
||||
return service_end_date <= (compare_date - YEARLY_GRACE_PERIOD)
|
||||
if duration_period == "years":
|
||||
logger.debug('Yearly license expires on %s', service_end_date)
|
||||
return service_end_date <= (compare_date - YEARLY_GRACE_PERIOD)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -128,9 +138,15 @@ def decode_license(license_contents, public_key_instance=None):
|
|||
""" Decodes the specified license contents, returning the decoded license. """
|
||||
license_public_key = public_key_instance or _PROD_LICENSE_PUBLIC_KEY
|
||||
try:
|
||||
decoded = jwt.decode(license_contents, key=license_public_key)
|
||||
jwt_data = jwt.decode(license_contents, key=license_public_key)
|
||||
except jwt.exceptions.DecodeError as de:
|
||||
logger.exception('Could not decode license file')
|
||||
raise LicenseDecodeError('Could not decode license found: %s' % de.message)
|
||||
|
||||
try:
|
||||
decoded = json.loads(jwt_data.get('license', '{}'))
|
||||
except ValueError as ve:
|
||||
logger.exception('Could not decode license file')
|
||||
raise LicenseDecodeError('Could not decode license found: %s' % ve.message)
|
||||
|
||||
return License(decoded)
|
||||
|
|
|
@ -46,6 +46,9 @@ class TestConfigProvider(BaseProvider):
|
|||
def save_volume_file(self, filename, flask_file):
|
||||
self.files[filename] = ''
|
||||
|
||||
def write_volume_file(self, filename, contents):
|
||||
self.files[filename] = contents
|
||||
|
||||
def get_volume_file(self, filename, mode='r'):
|
||||
if filename in REAL_FILES:
|
||||
return open(filename, mode=mode)
|
||||
|
|
Reference in a new issue