| # |
| # 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 |