Subscription and notification interfaces add http basic auth support
[modeling/etsicatalog.git] / catalog / packages / biz / vnf_pkg_subscription.py
1 # Copyright (C) 2019 Verizon. All Rights Reserved
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import ast
16 import json
17 import logging
18 import os
19 import requests
20 import uuid
21
22 from collections import Counter
23 from rest_framework import status
24 from requests.auth import HTTPBasicAuth
25 from catalog.packages import const
26 from catalog.pub.database.models import VnfPkgSubscriptionModel
27 from catalog.pub.exceptions import VnfPkgSubscriptionException, \
28     VnfPkgDuplicateSubscriptionException, SubscriptionDoesNotExistsException
29 from catalog.pub.utils.values import ignore_case_get
30
31 logger = logging.getLogger(__name__)
32
33 ROOT_FILTERS = {
34     "notificationTypes": "notification_types",
35     "vnfdId": "vnfd_id",
36     "vnfPkgId": "vnf_pkg_id",
37     "operationalState": "operation_states",
38     "usageState": "usage_states"
39 }
40
41
42 def is_filter_type_equal(new_filter, existing_filter):
43     return Counter(new_filter) == Counter(existing_filter)
44
45
46 class CreateSubscription(object):
47
48     def __init__(self, data):
49         self.data = data
50         self.filter = ignore_case_get(self.data, "filter", {})
51         self.callback_uri = ignore_case_get(self.data, "callbackUri")
52         self.authentication = ignore_case_get(self.data, "authentication", {})
53         self.notification_types = ignore_case_get(self.filter, "notificationTypes", [])
54         self.operation_states = ignore_case_get(self.filter, "operationalState", [])
55         self.usage_states = ignore_case_get(self.filter, "usageState", [])
56         self.vnfd_id = ignore_case_get(self.filter, "vnfdId", [])
57         self.vnf_pkg_id = ignore_case_get(self.filter, "vnfPkgId", [])
58         self.vnf_products_from_provider = \
59             ignore_case_get(self.filter, "vnfProductsFromProviders", [])
60
61     def check_callbackuri_connection(self):
62         logger.debug("SubscribeNotification-post::> Sending GET request "
63                      "to %s" % self.callback_uri)
64         try:
65             if self.authentication:
66                 if const.BASIC in self.authentication.get("authType", ''):
67                     params = self.authentication.get("paramsBasic", {})
68                     username = params.get("userName")
69                     password = params.get("password")
70                     response = requests.get(self.callback_uri, auth=HTTPBasicAuth(username, password), timeout=2)
71                 elif const.OAUTH2_CLIENT_CREDENTIALS in self.authentication.get("authType", ''):
72                     # todo
73                     pass
74                 else:
75                     # todo
76                     pass
77             else:
78                 response = requests.get(self.callback_uri, timeout=2)
79             if response.status_code != status.HTTP_204_NO_CONTENT:
80                 raise VnfPkgSubscriptionException(
81                     "callbackUri %s returns %s status code." % (
82                         self.callback_uri,
83                         response.status_code
84                     )
85                 )
86         except Exception:
87             raise VnfPkgSubscriptionException(
88                 "callbackUri %s didn't return 204 status code." % self.callback_uri
89             )
90
91     def do_biz(self):
92         self.subscription_id = str(uuid.uuid4())
93         self.check_callbackuri_connection()
94         self.check_valid_auth_info()
95         self.check_valid()
96         self.save_db()
97         subscription = VnfPkgSubscriptionModel.objects.get(
98             subscription_id=self.subscription_id
99         )
100         if subscription:
101             return subscription.toDict()
102
103     def check_valid_auth_info(self):
104         logger.debug("SubscribeNotification--post::> Validating Auth "
105                      "details if provided")
106         if self.authentication.get("paramsBasic", {}) and \
107                 const.BASIC not in self.authentication.get("authType"):
108             raise VnfPkgSubscriptionException('Auth type should be ' + const.BASIC)
109         if self.authentication.get("paramsOauth2ClientCredentials", {}) and \
110                 const.OAUTH2_CLIENT_CREDENTIALS not in self.authentication.get("authType"):
111             raise VnfPkgSubscriptionException('Auth type should be ' + const.OAUTH2_CLIENT_CREDENTIALS)
112
113     def check_filter_exists(self, sub):
114         # Check the usage states, operationStates
115         for filter_type in ["operation_states", "usage_states"]:
116             if not is_filter_type_equal(getattr(self, filter_type),
117                                         ast.literal_eval(getattr(sub, filter_type))):
118                 return False
119         # If all the above types are same then check id filter
120         for id_filter in ["vnfd_id", "vnf_pkg_id"]:
121             if not is_filter_type_equal(getattr(self, id_filter),
122                                         ast.literal_eval(getattr(sub, id_filter))):
123                 return False
124         return True
125
126     def check_valid(self):
127         logger.debug("SubscribeNotification--post::> Checking DB if "
128                      "callbackUri already exists")
129         subscriptions = VnfPkgSubscriptionModel.objects.filter(callback_uri=self.callback_uri)
130         if not subscriptions.exists():
131             return True
132         for subscription in subscriptions:
133             if self.check_filter_exists(subscription):
134                 raise VnfPkgDuplicateSubscriptionException(
135                     "Already Subscription (%s) exists with the "
136                     "same callbackUri and filter" % subscription.subscription_id)
137         return True
138
139     def save_db(self):
140         logger.debug("SubscribeNotification--post::> Saving the subscription "
141                      "%s to the database" % self.subscription_id)
142         links = {
143             "self": {
144                 "href": os.path.join(const.VNFPKG_SUBSCRIPTION_ROOT_URI, self.subscription_id)
145             }
146         }
147         VnfPkgSubscriptionModel.objects.create(
148             subscription_id=self.subscription_id,
149             callback_uri=self.callback_uri,
150             notification_types=json.dumps(self.notification_types),
151             auth_info=json.dumps(self.authentication),
152             usage_states=json.dumps(self.usage_states),
153             operation_states=json.dumps(self.operation_states),
154             vnf_products_from_provider=json.dumps(self.vnf_products_from_provider),
155             vnfd_id=json.dumps(self.vnfd_id),
156             vnf_pkg_id=json.dumps(self.vnf_pkg_id),
157             links=json.dumps(links))
158         logger.debug('Create Subscription[%s] success', self.subscription_id)
159
160
161 class QuerySubscription(object):
162
163     def query_multi_subscriptions(self, params):
164         query_data = {}
165         logger.debug("QuerySubscription--get--multi--subscriptions--biz::> Check "
166                      "for filter in query params %s" % params)
167         for query, value in list(params.items()):
168             if query in ROOT_FILTERS:
169                 query_data[ROOT_FILTERS[query] + '__icontains'] = value
170         # Query the database with filter if the request has fields in request params, else fetch all records
171         if query_data:
172             subscriptions = VnfPkgSubscriptionModel.objects.filter(**query_data)
173         else:
174             subscriptions = VnfPkgSubscriptionModel.objects.all()
175         if not subscriptions.exists():
176             return []
177         return [subscription.toDict() for subscription in subscriptions]
178
179     def query_single_subscription(self, subscription_id):
180         logger.debug("QuerySingleSubscriptions--get--single--subscription--biz::> "
181                      "ID: %s" % subscription_id)
182
183         subscription = VnfPkgSubscriptionModel.objects.filter(
184             subscription_id=subscription_id)
185         if not subscription.exists():
186             raise SubscriptionDoesNotExistsException("Subscription with ID: %s "
187                                                      "does not exist" % subscription_id)
188         return subscription[0].toDict()
189
190
191 class TerminateSubscription(object):
192
193     def terminate(self, subscription_id):
194         logger.debug("TerminateSubscriptions--delete--biz::> "
195                      "ID: %s" % subscription_id)
196
197         subscription = VnfPkgSubscriptionModel.objects.filter(
198             subscription_id=subscription_id)
199         if not subscription.exists():
200             raise SubscriptionDoesNotExistsException("Subscription with ID: %s "
201                                                      "does not exist" % subscription_id)
202         subscription[0].delete()