From 5db42511148a7e9ffc7d260ab94ac0b200d5b718 Mon Sep 17 00:00:00 2001 From: Marcus Kok <47163063+Marcusk19@users.noreply.github.com> Date: Mon, 18 Mar 2024 09:15:46 -0400 Subject: [PATCH] marketplace: add expiration check to org subscription operations (PROJQUAY-6716) (#2696) add expiration check to org subscription operations --- endpoints/api/billing.py | 23 +++++++++--- endpoints/api/organization.py | 11 ++++-- test/test_api_usage.py | 8 +++++ util/marketplace.py | 19 +++++----- util/test/test_marketplace.py | 67 +++++++++++++++++++++++++++++++++-- 5 files changed, 112 insertions(+), 16 deletions(-) diff --git a/endpoints/api/billing.py b/endpoints/api/billing.py index d32037e2d..245d72499 100644 --- a/endpoints/api/billing.py +++ b/endpoints/api/billing.py @@ -3,6 +3,7 @@ Billing information, subscriptions, and plan information. """ import datetime import json +import time import uuid import stripe @@ -55,7 +56,15 @@ def check_internal_api_for_subscription(namespace_user): for subscription in org_subscriptions: quantity = 1 if subscription.get("quantity") is None else subscription["quantity"] subscription_id = subscription["subscription_id"] - sku = marketplace_subscriptions.get_subscription_sku(subscription_id) + subscription_details = marketplace_subscriptions.get_subscription_details( + subscription_id + ) + sku = subscription_details["sku"] + expiration = subscription_details["expiration_date"] + now_ms = time.time() * 1000 + if expiration < now_ms: + organization_skus.remove_subscription_from_org(namespace_user.id, subscription_id) + continue for x in range(quantity): plans.append(get_plan_using_rh_sku(sku)) pass @@ -957,11 +966,17 @@ class OrganizationRhSku(ApiResource): if query: subscriptions = list(query.dicts()) for subscription in subscriptions: - subscription_sku = marketplace_subscriptions.get_subscription_sku( + subscription_details = marketplace_subscriptions.get_subscription_details( subscription["subscription_id"] ) - subscription["sku"] = subscription_sku - subscription["metadata"] = get_plan_using_rh_sku(subscription_sku) + now_ms = time.time() * 1000 + if subscription_details["expiration_date"] < now_ms: + model.organization_skus.remove_subscription_from_org( + organization.id, subscription["subscription_id"] + ) + continue + subscription["sku"] = subscription_details["sku"] + subscription["metadata"] = get_plan_using_rh_sku(subscription_details["sku"]) if subscription.get("quantity") is None: subscription["quantity"] = 1 return subscriptions diff --git a/endpoints/api/organization.py b/endpoints/api/organization.py index 995b42f4b..493d3616c 100644 --- a/endpoints/api/organization.py +++ b/endpoints/api/organization.py @@ -3,6 +3,7 @@ Manage organizations, members and OAuth applications. """ import logging +import time import recaptcha2 from flask import request @@ -378,11 +379,17 @@ class OrgPrivateRepositories(ApiResource): if features.RH_MARKETPLACE: query = organization_skus.get_org_subscriptions(organization.id) rh_subscriptions = list(query.dicts()) if query is not None else [] + now_ms = time.time() * 1000 for subscription in rh_subscriptions: - subscription_sku = marketplace_subscriptions.get_subscription_sku( + subscription_details = marketplace_subscriptions.get_subscription_details( subscription["subscription_id"] ) - equivalent_stripe_plan = get_plan_using_rh_sku(subscription_sku) + if subscription_details["expiration_date"] < now_ms: + organization_skus.remove_subscription_from_org( + organization.id, subscription["subscription_id"] + ) + continue + equivalent_stripe_plan = get_plan_using_rh_sku(subscription_details["sku"]) if equivalent_stripe_plan: if subscription.get("quantity") is None: quantity = 1 diff --git a/test/test_api_usage.py b/test/test_api_usage.py index 345270ce9..27ea39160 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -5183,6 +5183,14 @@ class TestOrganizationRhSku(ApiTestCase): plans = check_internal_api_for_subscription(org) assert len(plans) == 1 + def test_expired_attachment(self): + self.login(SUBSCRIPTION_USER) + user = model.user.get_user(SUBSCRIPTION_USER) + org = model.organization.get_organization(SUBSCRIPTION_ORG) + model.organization_skus.bind_subscription_to_org(80808080, org.id, user.id, 1) + json = self.getJsonResponse(OrgPrivateRepositories, params=dict(orgname=SUBSCRIPTION_ORG)) + self.assertEqual(json["privateAllowed"], False) + class TestUserSku(ApiTestCase): def test_get_user_skus(self): diff --git a/util/marketplace.py b/util/marketplace.py index 0fe987a9c..c007324b8 100644 --- a/util/marketplace.py +++ b/util/marketplace.py @@ -191,9 +191,9 @@ class RedHatSubscriptionApi(object): return r.status_code - def get_subscription_sku(self, subscription_id): + def get_subscription_details(self, subscription_id): """ - Return the sku for a specific subscription + Return the sku and expiration date for a specific subscription """ request_url = f"{self.marketplace_endpoint}/subscription/v5/products/subscription_id={subscription_id}" request_headers = {"Content-Type": "application/json"} @@ -210,8 +210,9 @@ class RedHatSubscriptionApi(object): info = json.loads(r.content) - SubscriptionSKU = info[0]["sku"] - return SubscriptionSKU + subscription_sku = info[0]["sku"] + expiration_date = info[1]["activeEndDate"] + return {"sku": subscription_sku, "expiration_date": expiration_date} except requests.exceptions.SSLError: raise requests.exceptions.SSLError except requests.exceptions.ReadTimeout: @@ -267,7 +268,7 @@ TEST_USER = { "subscriptionNumber": "12399889", "quantity": 2, "effectiveStartDate": 1707368400000, - "effectiveEndDate": 3813177600, + "effectiveEndDate": 3813177600000, }, { "id": 11223344, @@ -282,7 +283,7 @@ TEST_USER = { "subscriptionNumber": "12399889", "quantity": 1, "effectiveStartDate": 1707368400000, - "effectiveEndDate": 3813177600, + "effectiveEndDate": 3813177600000, }, ], } @@ -329,10 +330,12 @@ class FakeSubscriptionApi(RedHatSubscriptionApi): def extend_subscription(self, subscription_id, end_date): self.subscription_extended = True - def get_subscription_sku(self, subscription_id): + def get_subscription_details(self, subscription_id): valid_ids = [subscription["id"] for subscription in TEST_USER["subscriptions"]] if subscription_id in valid_ids: - return "MW02701" + return {"sku": "MW02701", "expiration_date": 3813177600000} + elif subscription_id == 80808080: + return {"sku": "MW02701", "expiration_date": 1645544830000} else: return None diff --git a/util/test/test_marketplace.py b/util/test/test_marketplace.py index 426ab03bd..aee9b0ba4 100644 --- a/util/test/test_marketplace.py +++ b/util/test/test_marketplace.py @@ -140,6 +140,60 @@ mocked_subscription_response = [ }, ] +mocked_expired_sub = [ + { + "id": 41619474, + "masterEndSystemName": "SUBSCRIPTION", + "createdEndSystemName": "SUBSCRIPTION", + "createdByUserName": None, + "createdDate": 1708616554000, + "lastUpdateEndSystemName": "SUBSCRIPTION", + "lastUpdateUserName": None, + "lastUpdateDate": 1708616554000, + "externalCreatedDate": None, + "externalLastUpdateDate": None, + "activeStartDate": None, + "activeEndDate": None, + "inactiveDate": None, + "signedDate": None, + "terminatedDate": None, + "renewedDate": None, + "parentSubscriptionProductId": None, + "externalOrderSystemName": "SUBSCRIPTION", + "externalOrderNumber": None, + "status": None, + "sku": "MW02701", + "childrenIds": [41619475], + "serviceable": False, + }, + { + "id": 41619475, + "masterEndSystemName": "SUBSCRIPTION", + "createdEndSystemName": "SUBSCRIPTION", + "createdByUserName": None, + "createdDate": 1645544830000, + "lastUpdateEndSystemName": "SUBSCRIPTION", + "lastUpdateUserName": None, + "lastUpdateDate": 1645544830000, + "externalCreatedDate": None, + "externalLastUpdateDate": None, + "activeStartDate": 1645544830000, + "activeEndDate": 1645544830000, + "inactiveDate": None, + "signedDate": None, + "terminatedDate": None, + "renewedDate": None, + "parentSubscriptionProductId": 41619474, + "externalOrderSystemName": "SUBSCRIPTION", + "externalOrderNumber": None, + "status": "active", + "oracleInventoryOrgId": None, + "sku": "SVCMW02701", + "childrenIds": None, + "serviceable": True, + }, +] + class TestMarketplace(unittest.TestCase): @patch("requests.request") @@ -152,8 +206,8 @@ class TestMarketplace(unittest.TestCase): assert customer_id is None subscription_response = subscription_api.lookup_subscription(123456, "sku") assert subscription_response is None - subscription_sku = subscription_api.get_subscription_sku(123456) - assert subscription_sku is None + subscription_details = subscription_api.get_subscription_details(123456) + assert subscription_details is None extended_subscription = subscription_api.extend_subscription(12345, 102623) assert extended_subscription is None create_subscription_response = subscription_api.create_entitlement(12345, "sku") @@ -178,3 +232,12 @@ class TestMarketplace(unittest.TestCase): subscriptions = subscription_api.lookup_subscription(12345, "some_sku") assert len(subscriptions) == 2 + + @patch("requests.request") + def test_subscription_details(self, requests_mock): + subscription_api = RedHatSubscriptionApi(app_config) + requests_mock.return_value.content = json.dumps(mocked_expired_sub) + + subscription_details = subscription_api.get_subscription_details(12345) + assert subscription_details["sku"] == "MW02701" + assert subscription_details["expiration_date"] == 1645544830000