Base net-virt CLI files on top of which ONOS specific changes will be done
diff --git a/cli/sdncon/clusterAdmin/middleware.py b/cli/sdncon/clusterAdmin/middleware.py
new file mode 100755
index 0000000..f66b367
--- /dev/null
+++ b/cli/sdncon/clusterAdmin/middleware.py
@@ -0,0 +1,269 @@
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+import sys, re
+from django.conf import settings
+from django.contrib.auth.views import login
+from django.http import HttpResponseRedirect, HttpResponse
+from django.utils import simplejson
+from django.contrib import auth
+from django.contrib.auth.models import User, AnonymousUser
+
+from .utils import isCloudBuild
+from .models import CustomerUser, Customer, Cluster, AuthToken
+
+import logging
+debugLevel = logging.INFO
+logfile = None
+
+# For testing, uncomment as required
+#debugLevel = logging.DEBUG
+#logfile = 'middleware.log'
+
+def initLogger():
+ logger = logging.getLogger('middleware')
+ formatter = logging.Formatter('%(asctime)s [%(name)s] %(levelname)s %(message)s')
+ logger.setLevel(debugLevel)
+
+ # Add a file handler
+ if logfile:
+ file_handler = logging.FileHandler(logfile)
+ file_handler.setFormatter(formatter)
+ logger.addHandler(file_handler)
+
+ # Add a console handler
+ console_handler = logging.StreamHandler()
+ console_handler.setFormatter(formatter)
+ console_handler.setLevel(debugLevel)
+ logger.addHandler(console_handler)
+ return logger
+
+logger = initLogger()
+
+def is_localhost(request):
+ return request.META['REMOTE_ADDR'] in ['127.0.0.1', '127.0.1.1', 'localhost']
+
+class RequireAuthMiddleware(object):
+ """RequireAuthMiddleware: Middleware to enforce authentication
+
+ If it is enabled, every Django-powered page, except LOGIN_URL and the list of EXEMPT_URLS,
+ will require authentication
+
+ Unautenticated user requests are redirected to the login page set (LOGIN_URL in settings)
+ Unautenticated REST calls which are returned a JSON error as a 403 forbidden response
+ """
+
+ def __init__(self):
+ self.enforce_auth = isCloudBuild() # For now, enforce authentication only if we are a cloud instance
+ self.login_url = getattr(settings, 'LOGIN_URL', '/accounts/login/')
+ self.exempt_urls = [self.login_url]
+ self.exempt_urls += getattr(settings, 'EXEMPT_URLS', [] )
+ self.rest_prefix = getattr(settings, 'REST_PREFIX', '/rest/')
+ if self.enforce_auth:
+ logger.info('RequireAuthMiddleware: Enforcing Authentication')
+
+ def process_request(self, request):
+ if self.enforce_auth:
+ if is_localhost(request):
+ return None
+ if request.user.is_anonymous():
+ for url in self.exempt_urls:
+ if request.path.startswith(url):
+ return None
+ return self.redirect(request)
+ return None
+
+ def process_response(self, request, response):
+ if request.path.startswith(self.login_url):
+ response['x-bsc-auth-status'] = 'required'
+ return response
+
+ def redirect(self, request):
+ if self.rest_prefix in request.path:
+ logger.warn('RequireAuthMiddleware: Unauthenticated REST request: %s, %s, %s' % (
+ request.path, str(request.user), request.META['REMOTE_ADDR']))
+ json_content_type = 'application/json'
+ json_error_response = {'error_type': 'auth', 'description': 'Authentication error'}
+ return HttpResponse(simplejson.dumps(json_error_response), json_content_type, 403)
+
+ logger.debug('RequireAuthMiddleware: Redirecting to login page: %s, %s, %s' % (
+ request.path, str(request.user), request.META['REMOTE_ADDR']))
+ return HttpResponseRedirect('%s?next=%s' % (self.login_url, request.path))
+
+
+class ClusterAuthenticate(object):
+ """ClusterAuthenticate: Middleware that authenticates/sets credentials for the REST requests
+
+ If this is a REST request:
+ If request is from localhost
+ authorize it and set the user as admin
+ If a the user already has an authenticated session
+ validate that this user is authorized to access the requested cluster
+ If it is from an anonynous user:
+ extract auth token from the request parameters and
+ verify that the token is in authorized for the requested cluster
+ (for now, just validate that the user associated with the token
+ is authorized to access the requested cluster. In future, we may do more)
+ """
+
+ token_param_name = 'auth-token' # Name of the auth token parameter in the query string
+ enforce_auth = isCloudBuild() # For now, enfore auth only for the cloud instance
+ bypass_localhost_check = False # Set to true to force auth token check even on localhost
+
+ def process_request(self, request):
+ logger.debug('url: ' + request.path)
+ if not self.enforce_auth:
+ logger.debug('ClusterAuthenticate ignored: is disabled')
+ return
+ if is_localhost(request) and not self.bypass_localhost_check:
+ logger.debug('ClusterAuthenticate ignored: is local request')
+ return
+ if self.get_req_cluster_name(request) is None:
+ logger.debug('ClusterAuthenticate ignored: no cluster name in request');
+ return
+
+ # Find user and autorized clusters
+ cluster_name = self.get_req_cluster_name(request)
+ cluster = self.get_cluster_from_name(cluster_name)
+ user = AnonymousUser()
+ if hasattr(request, 'session'):
+ user = auth.get_user(request)
+ logger.debug('User from request: ' + str(user))
+ if user.is_authenticated():
+ allowed_clusters = self.get_allowed_clusters_for_user(user)
+ else:
+ token_string = self.get_req_token_string(request)
+ user = self.get_user_for_token(token_string)
+ allowed_clusters = self.get_allowed_clusters_for_token(token_string)
+ logger.debug('Checking authorization for cluster ' + str(cluster) +
+ ' in cluster list ' + str(allowed_clusters) +
+ ' for ' + str(user))
+
+ # Do validation/set user for request
+ if hasattr(request, 'user'):
+ request.user = AnonymousUser()
+ request._cached_user = AnonymousUser()
+ if cluster and allowed_clusters:
+ if cluster in allowed_clusters:
+ request.user = user
+ request._cached_user = user
+ return
+
+ def validate_session(self, request):
+ return True
+
+ def get_req_cluster_name(self, request):
+ # Find last entry in path, remove parameters
+ path = request.path
+ cluster_name = None
+ try:
+ pathcomps = path.split('/')
+ if 'rest' in (pathcomps[1],pathcomps[2]):
+ cluster_name_idx = 5
+ if pathcomps[cluster_name_idx].find(':') > -1:
+ cluster_name = pathcomps[cluster_name_idx]
+ except (TypeError, IndexError):
+ logger.debug('Type error parsing path for customer: ' + str(path))
+ return None
+ except Exception:
+ logger.debug('Unknown error parsing path for customer: ' + str(path))
+ return None
+ return cluster_name
+
+ def get_cluster_from_name(self, cluster_name):
+ if cluster_name:
+ for cluster in Cluster.objects.all():
+ if cluster_name == cluster.id:
+ logger.debug('Mapped request to cluster "%s"' % cluster_name)
+ return cluster
+ logger.warn('Failed to map request to cluster: "%s"' % cluster_name)
+ return None
+
+ def get_req_token_string(self, request):
+ token_string = None
+ try:
+ token_string = request.REQUEST[self.token_param_name]
+ token_string = token_string.upper()
+ except KeyError:
+ logger.debug('No token param in request')
+ return token_string
+
+ def get_token_from_name(self, token_string):
+ token = None
+ if token_string:
+ try:
+ token = AuthToken.objects.get(id=token_string)
+ except AuthToken.DoesNotExist:
+ logger.debug('Token not found in DB: ' + str(token_string))
+ token = None
+ except Exception:
+ logger.debug('Auth Tokens not configured in DB? - ' + str(token_string))
+ token = None
+ return token
+
+ def get_user_for_token(self, token_string):
+ user = AnonymousUser()
+ token = self.get_token_from_name(token_string)
+ if token:
+ user = token.user
+ return user
+
+ def get_allowed_clusters_for_token(self, token_string):
+ """Given an auth token, generate the list of clusters for which it gives credentials
+
+ If the cluster entry in the token object is present, use that
+ If the customer entry in the token object is present, return all clusters for the customer
+ If the user entry in the token object is present, use that to get a list of clusters.
+
+ In the future, DB changes may provide varying granularity.
+ """
+
+ logger.debug('Got token: ' + str(token_string))
+
+ cluster_list = []
+ token = self.get_token_from_name(token_string)
+ if token:
+ if token.cluster is not None:
+ logger.debug('token mapped to cluster ' + str(token.cluster))
+ cluster_list = [token.cluster]
+ elif token.customer is not None:
+ logger.debug('token mapped to customer ' + str(token.customer))
+ cluster_list = Cluster.objects.filter(customer=token.customer)
+ else:
+ logger.debug('token mapped to user ' + str(token.user))
+ cluster_list = self.get_allowed_clusters_for_user(token.user)
+
+ logger.debug('Returning clust list ' + str(cluster_list))
+ return cluster_list
+
+ def get_allowed_clusters_for_user(self, user):
+ """Given a user, generate the list of clusters for which it gives credentials
+ Use user to map to a customer (list) and from there to a list of clusters.
+ """
+
+ logger.debug('Got user: ' + str(user))
+
+ cluster_list = []
+ if user:
+ for cust_user in CustomerUser.objects.filter(user=user):
+ # Currently the query below isn't working. Brute force alternative given
+ #cluster_list.extend(Cluster.objects.filter(customer=cust_user.customer))
+ for cluster in Cluster.objects.all():
+ if cluster.customer == cust_user.customer:
+ cluster_list.append(cluster)
+
+ logger.debug('Returning cluster list ' + str(cluster_list))
+ return cluster_list