1 ##############################################################################
2 # Copyright (c) 2016 Max Breitenfeldt and others.
3 # Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 ##############################################################################
15 import oauth2 as oauth
16 from django.conf import settings
17 from django.utils import timezone
18 from django.contrib import messages
19 from django.contrib.auth import logout, authenticate, login
20 from django.contrib.auth.decorators import login_required
21 from django.contrib.auth.mixins import LoginRequiredMixin
22 from django.contrib.auth.models import User
23 from django.urls import reverse
24 from django.http import HttpResponse
25 from django.shortcuts import get_object_or_404
26 from django.utils.decorators import method_decorator
27 from django.views.generic import RedirectView, TemplateView, UpdateView
28 from django.shortcuts import render
30 from rest_framework.authtoken.models import Token
31 from mozilla_django_oidc.auth import OIDCAuthenticationBackend
34 from account.forms import AccountSettingsForm
35 from account.jira_util import SignatureMethod_RSA_SHA1
36 from account.models import UserProfile
37 from booking.models import Booking
38 from resource_inventory.models import ResourceTemplate, Image
41 @method_decorator(login_required, name='dispatch')
42 class AccountSettingsView(UpdateView):
44 form_class = AccountSettingsForm
45 template_name_suffix = '_update_form'
47 def get_success_url(self):
48 messages.add_message(self.request, messages.INFO,
52 def get_object(self, queryset=None):
53 return self.request.user.userprofile
55 def get_context_data(self, **kwargs):
56 token, created = Token.objects.get_or_create(user=self.request.user)
57 context = super(AccountSettingsView, self).get_context_data(**kwargs)
58 context.update({'title': "Settings", 'token': token})
62 class MyOIDCAB(OIDCAuthenticationBackend):
63 def filter_users_by_claims(self, claims):
65 Checks to see if user exists and create user if not
67 Linux foundation does not allow users to change their
68 username, so chose to match users based on their username.
69 If this changes we will need to match users based on some
72 username = claims.get(os.environ['CLAIMS_ENDPOINT'] + 'username')
75 return HttpResponse('No username provided, contact support.')
78 # For literally no (good) reason user needs to be a queryset
79 user = User.objects.filter(username=username)
81 except User.DoesNotExist:
82 return self.UserModel.objects.none()
84 def create_user(self, claims):
85 """ This creates a user and user profile"""
86 user = super(MyOIDCAB, self).create_user(claims)
87 user.username = claims.get(os.environ['CLAIMS_ENDPOINT'] + 'username')
92 up.email_addr = claims.get('email')
96 def update_user(self, user, claims):
97 """ If their account has different email, change the email """
98 up = UserProfile.objects.get(user=user)
99 up.email_addr = claims.get('email')
104 class JiraLoginView(RedirectView):
105 def get_redirect_url(self, *args, **kwargs):
106 consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET)
107 client = oauth.Client(consumer)
108 client.set_signature_method(SignatureMethod_RSA_SHA1())
110 # Step 1. Get a request token from Jira.
112 resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST")
114 messages.add_message(self.request, messages.ERROR,
115 'Error: Connection to Jira failed. Please contact an Administrator')
117 if resp['status'] != '200':
118 messages.add_message(self.request, messages.ERROR,
119 'Error: Connection to Jira failed. Please contact an Administrator')
122 # Step 2. Store the request token in a session for later use.
123 self.request.session['request_token'] = dict(urllib.parse.parse_qsl(content.decode()))
124 # Step 3. Redirect the user to the authentication URL.
125 url = settings.OAUTH_AUTHORIZE_URL + '?oauth_token=' + \
126 self.request.session['request_token']['oauth_token'] + \
127 '&oauth_callback=' + settings.OAUTH_CALLBACK_URL
131 class JiraLogoutView(LoginRequiredMixin, RedirectView):
132 def get_redirect_url(self, *args, **kwargs):
137 class JiraAuthenticatedView(RedirectView):
138 def get_redirect_url(self, *args, **kwargs):
139 # Step 1. Use the request token in the session to build a new client.
140 consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET)
141 token = oauth.Token(self.request.session['request_token']['oauth_token'],
142 self.request.session['request_token']['oauth_token_secret'])
143 client = oauth.Client(consumer, token)
144 client.set_signature_method(SignatureMethod_RSA_SHA1())
146 # Step 2. Request the authorized access token from Jira.
148 resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST")
150 messages.add_message(self.request, messages.ERROR,
151 'Error: Connection to Jira failed. Please contact an Administrator')
153 if resp['status'] != '200':
154 messages.add_message(self.request, messages.ERROR,
155 'Error: Connection to Jira failed. Please contact an Administrator')
158 access_token = dict(urllib.parse.parse_qsl(content.decode()))
160 module_dir = os.path.dirname(__file__) # get current directory
161 with open(module_dir + '/rsa.pem', 'r') as f:
165 'access_token': access_token['oauth_token'],
166 'access_token_secret': access_token['oauth_token_secret'],
167 'consumer_key': settings.OAUTH_CONSUMER_KEY,
171 jira = JIRA(server=settings.JIRA_URL, oauth=oauth_dict)
172 username = jira.current_user()
175 email = jira.user(username).emailAddress
176 except AttributeError:
179 # Step 3. Lookup the user or create them if they don't exist.
181 user = User.objects.get(username=username)
182 except User.DoesNotExist:
183 # Save our permanent token and secret for later.
184 user = User.objects.create_user(username=username,
185 password=access_token['oauth_token_secret'])
186 profile = UserProfile()
189 user.userprofile.email_addr = email
190 url = reverse('account:settings')
191 user.userprofile.oauth_token = access_token['oauth_token']
192 user.userprofile.oauth_secret = access_token['oauth_token_secret']
193 user.userprofile.save()
194 user.set_password(access_token['oauth_token_secret'])
196 user = authenticate(username=username, password=access_token['oauth_token_secret'])
197 login(self.request, user)
198 # redirect user to settings page to complete profile
202 @method_decorator(login_required, name='dispatch')
203 class UserListView(TemplateView):
204 template_name = "account/user_list.html"
206 def get_context_data(self, **kwargs):
207 users = User.objects.all()
208 context = super(UserListView, self).get_context_data(**kwargs)
209 context.update({'title': "Dashboard Users", 'users': users})
213 def account_detail_view(request):
214 template = "account/details.html"
215 return render(request, template)
218 def account_resource_view(request):
220 Display a user's resources.
222 gathers a users genericResoureBundles and
223 turns them into displayable objects
225 if not request.user.is_authenticated:
226 return render(request, "dashboard/login.html", {'title': 'Authentication Required'})
227 template = "account/resource_list.html"
229 active_bundles = [book.resource for book in Booking.objects.filter(
230 owner=request.user, end__gte=timezone.now())]
231 active_resources = [bundle.template.id for bundle in active_bundles]
232 resource_list = list(ResourceTemplate.objects.filter(owner=request.user))
235 "resources": resource_list,
236 "active_resources": active_resources,
237 "title": "My Resources"
239 return render(request, template, context=context)
242 def account_booking_view(request):
243 if not request.user.is_authenticated:
244 return render(request, "dashboard/login.html", {'title': 'Authentication Required'})
245 template = "account/booking_list.html"
246 bookings = list(Booking.objects.filter(owner=request.user, end__gt=timezone.now()).order_by("-start"))
247 my_old_bookings = Booking.objects.filter(owner=request.user, end__lt=timezone.now()).order_by("-start")
248 collab_old_bookings = request.user.collaborators.filter(end__lt=timezone.now()).order_by("-start")
249 expired_bookings = list(my_old_bookings.union(collab_old_bookings))
250 collab_bookings = list(request.user.collaborators.filter(end__gt=timezone.now()).order_by("-start"))
252 "title": "My Bookings",
253 "bookings": bookings,
254 "collab_bookings": collab_bookings,
255 "expired_bookings": expired_bookings
257 return render(request, template, context=context)
260 def account_configuration_view(request):
261 if not request.user.is_authenticated:
262 return render(request, "dashboard/login.html", {'title': 'Authentication Required'})
263 template = "account/configuration_list.html"
264 configs = list(ResourceTemplate.objects.filter(owner=request.user))
265 context = {"title": "Configuration List", "configurations": configs}
266 return render(request, template, context=context)
269 def account_images_view(request):
270 if not request.user.is_authenticated:
271 return render(request, "dashboard/login.html", {'title': 'Authentication Required'})
272 template = "account/image_list.html"
273 my_images = Image.objects.filter(owner=request.user)
274 public_images = Image.objects.filter(public=True)
276 for image in my_images:
278 used_images[image.id] = "true"
282 "public_images": public_images,
283 "used_images": used_images
285 return render(request, template, context=context)
288 def resource_delete_view(request, resource_id=None):
289 if not request.user.is_authenticated:
290 return HttpResponse('no') # 403?
291 grb = get_object_or_404(ResourceTemplate, pk=resource_id)
292 if not request.user.id == grb.owner.id:
293 return HttpResponse('no') # 403?
294 if Booking.objects.filter(resource__template=grb, end__gt=timezone.now()).exists():
295 return HttpResponse('no') # 403?
297 return HttpResponse('')
300 def configuration_delete_view(request, config_id=None):
301 if not request.user.is_authenticated:
302 return HttpResponse('no') # 403?
303 config = get_object_or_404(ResourceTemplate, pk=config_id)
304 if not request.user.id == config.owner.id:
305 return HttpResponse('no') # 403?
306 if Booking.objects.filter(resource__template=config, end__gt=timezone.now()).exists():
307 return HttpResponse('no')
309 return HttpResponse('')
312 def booking_cancel_view(request, booking_id=None):
313 if not request.user.is_authenticated:
314 return HttpResponse('no') # 403?
315 booking = get_object_or_404(Booking, pk=booking_id)
316 if not request.user.id == booking.owner.id:
317 return HttpResponse('no') # 403?
319 if booking.end < timezone.now(): # booking already over
320 return HttpResponse('')
322 booking.end = timezone.now()
324 return HttpResponse('')
327 def image_delete_view(request, image_id=None):
328 if not request.user.is_authenticated:
329 return HttpResponse('no') # 403?
330 image = get_object_or_404(Image, pk=image_id)
331 if image.public or image.owner.id != request.user.id:
332 return HttpResponse('no') # 403?
333 # check if used in booking
335 return HttpResponse('no') # 403?
337 return HttpResponse('')