Near Stable Code commit
Large commit with current existing code base. The application mostly works, but for ease of review I'm merging a lot of my commits and will then do more consistent commits on top of this. This code base is ready for review as a whole and is unlikely to change drastically anymore. Change-Id: Iecf75c44da28d3be14964a205cbe44dc3d9d5619
This commit is contained in:
parent
745637d3da
commit
96cea92af0
|
@ -0,0 +1,3 @@
|
||||||
|
*.pyc
|
||||||
|
*.sqlite3
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from models import Token, Registration
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
admin.site.register(Token)
|
||||||
|
admin.site.register(Registration)
|
|
@ -0,0 +1,63 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import jsonfield.fields
|
||||||
|
import api_v1.models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Notification',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('notes', jsonfield.fields.JSONField(default={})),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('acknowledged', models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Registration',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.CharField(default=api_v1.models.hex_uuid, max_length=200, serialize=False, primary_key=True)),
|
||||||
|
('reg_ip', models.GenericIPAddressField()),
|
||||||
|
('keystone_user', jsonfield.fields.JSONField(default={})),
|
||||||
|
('action_notes', jsonfield.fields.JSONField(default={})),
|
||||||
|
('approved', models.BooleanField(default=False)),
|
||||||
|
('completed', models.BooleanField(default=False)),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('approved_on', models.DateTimeField(null=True)),
|
||||||
|
('completed_on', models.DateTimeField(null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Token',
|
||||||
|
fields=[
|
||||||
|
('token', models.CharField(max_length=200, serialize=False, primary_key=True)),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('expires', models.DateTimeField()),
|
||||||
|
('registration', models.ForeignKey(to='api_v1.Registration')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='notification',
|
||||||
|
name='registration',
|
||||||
|
field=models.ForeignKey(to='api_v1.Registration'),
|
||||||
|
preserve_default=True,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,109 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from uuid import uuid4
|
||||||
|
from django.utils import timezone
|
||||||
|
from jsonfield import JSONField
|
||||||
|
|
||||||
|
|
||||||
|
def hex_uuid():
|
||||||
|
return uuid4().hex
|
||||||
|
|
||||||
|
|
||||||
|
class Registration(models.Model):
|
||||||
|
"""
|
||||||
|
Wrapper object for the request and related actions.
|
||||||
|
Stores the state registration and a log for the
|
||||||
|
action.
|
||||||
|
"""
|
||||||
|
uuid = models.CharField(max_length=200, default=hex_uuid,
|
||||||
|
primary_key=True)
|
||||||
|
# who is this:
|
||||||
|
reg_ip = models.GenericIPAddressField()
|
||||||
|
keystone_user = JSONField(default={})
|
||||||
|
|
||||||
|
# Effectively a log of what the actions are doing.
|
||||||
|
action_notes = JSONField(default={})
|
||||||
|
|
||||||
|
approved = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
completed = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
created = models.DateTimeField(default=timezone.now)
|
||||||
|
approved_on = models.DateTimeField(null=True)
|
||||||
|
completed_on = models.DateTimeField(null=True)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Registration, self).__init__(*args, **kwargs)
|
||||||
|
# in memory dict to be used for passing data between actions:
|
||||||
|
self.cache = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actions(self):
|
||||||
|
return self.action_set.order_by('order')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
actions = []
|
||||||
|
for action in self.actions:
|
||||||
|
actions.append({
|
||||||
|
"action_name": action.action_name,
|
||||||
|
"data": action.action_data,
|
||||||
|
"valid": action.valid
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"ip_address": self.reg_ip, "notes": self.action_notes,
|
||||||
|
"approved": self.approved, "completed": self.completed,
|
||||||
|
"actions": actions, "uuid": self.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_action_note(self, action, note):
|
||||||
|
if action in self.action_notes:
|
||||||
|
self.action_notes[action].append(note)
|
||||||
|
else:
|
||||||
|
self.action_notes[action] = [note]
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Token(models.Model):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
registration = models.ForeignKey(Registration)
|
||||||
|
token = models.CharField(max_length=200, primary_key=True)
|
||||||
|
created = models.DateTimeField(default=timezone.now)
|
||||||
|
expires = models.DateTimeField()
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"registration": self.registration.uuid,
|
||||||
|
"token": self.token, "expires": self.expires
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Notification(models.Model):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
notes = JSONField(default={})
|
||||||
|
registration = models.ForeignKey(Registration)
|
||||||
|
created = models.DateTimeField(default=timezone.now)
|
||||||
|
acknowledged = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"notes": self.notes,
|
||||||
|
"registration": self.registration.uuid,
|
||||||
|
"acknowledged": self.acknowledged,
|
||||||
|
"created": self.created
|
||||||
|
}
|
|
@ -0,0 +1,649 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
from api_v1.models import Registration, Token
|
||||||
|
import mock
|
||||||
|
from django.utils import timezone
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
|
temp_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
class FakeManager(object):
|
||||||
|
|
||||||
|
def find_user(self, name):
|
||||||
|
global temp_cache
|
||||||
|
return temp_cache['users'].get(name, None)
|
||||||
|
|
||||||
|
def get_user(self, user_id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_user(self, name, password, email, project_id):
|
||||||
|
global temp_cache
|
||||||
|
user = mock.Mock()
|
||||||
|
temp_cache['i'] += 1
|
||||||
|
user.id = temp_cache['i']
|
||||||
|
user.name = name
|
||||||
|
user.password = password
|
||||||
|
user.email = email
|
||||||
|
user.default_project = project_id
|
||||||
|
temp_cache['users'][name] = user
|
||||||
|
return user
|
||||||
|
|
||||||
|
def update_user_password(self, user, password):
|
||||||
|
global temp_cache
|
||||||
|
user = temp_cache['users'][user.name]
|
||||||
|
user.password = password
|
||||||
|
|
||||||
|
def find_role(self, name):
|
||||||
|
global temp_cache
|
||||||
|
temp_cache['roles'].get(name, None)
|
||||||
|
|
||||||
|
def add_user_role(self, user, role, project_id):
|
||||||
|
project = self.get_project(project_id)
|
||||||
|
project.roles.append((user, role))
|
||||||
|
|
||||||
|
def find_project(self, project_name):
|
||||||
|
global temp_cache
|
||||||
|
return temp_cache['projects'].get(project_name, None)
|
||||||
|
|
||||||
|
def get_project(self, project_id):
|
||||||
|
global temp_cache
|
||||||
|
for project in temp_cache['projects'].values():
|
||||||
|
if project.id == project_id:
|
||||||
|
return project
|
||||||
|
|
||||||
|
def create_project(self, project_name, p_id=None):
|
||||||
|
global temp_cache
|
||||||
|
project = mock.Mock()
|
||||||
|
if p_id:
|
||||||
|
project.id = p_id
|
||||||
|
else:
|
||||||
|
temp_cache['i'] += 1
|
||||||
|
project.id = temp_cache['i']
|
||||||
|
project.name = project_name
|
||||||
|
project.roles = []
|
||||||
|
temp_cache['projects'][project_name] = project
|
||||||
|
return project
|
||||||
|
|
||||||
|
|
||||||
|
class APITests(APITestCase):
|
||||||
|
"""Tests to ensure the approval/token workflow does
|
||||||
|
what is expected. These test don't check final
|
||||||
|
results for actions, simply that the registrations,
|
||||||
|
action, and tokens are created/updated.
|
||||||
|
|
||||||
|
These tests also focus on authentication status
|
||||||
|
and role prermissions."""
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
def test_new_user(self):
|
||||||
|
"""
|
||||||
|
Ensure the new user workflow goes as expected.
|
||||||
|
Create registration, create token, submit token.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
project = mock.Mock()
|
||||||
|
project.id = 'test_project_id'
|
||||||
|
project.name = 'test_project'
|
||||||
|
project.roles = []
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {},
|
||||||
|
'projects': {'test_project': project},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/user"
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "project_owner,Member",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
data = {'email': "test@example.com", 'role': "Member",
|
||||||
|
'project_id': 'test_project_id'}
|
||||||
|
response = self.client.post(url, data, format='json', headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data, {'notes': ['created token']})
|
||||||
|
|
||||||
|
new_token = Token.objects.all()[0]
|
||||||
|
url = "/api_v1/token/" + new_token.token
|
||||||
|
data = {'password': 'testpassword'}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
def test_new_user_no_project(self):
|
||||||
|
"""
|
||||||
|
Can't create a user for a non-existent project.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {},
|
||||||
|
'projects': {},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/user"
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "project_owner,Member",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
data = {'email': "test@example.com", 'role': "Member",
|
||||||
|
'project_id': 'test_project_id'}
|
||||||
|
response = self.client.post(url, data, format='json', headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(response.data, {'notes': ['actions invalid']})
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
def test_new_user_not_my_project(self):
|
||||||
|
"""
|
||||||
|
Can't create a user for project that isn't mine.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {},
|
||||||
|
'projects': {},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/user"
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "Member",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
data = {'email': "test@example.com", 'role': "Member",
|
||||||
|
'project_id': 'test_project_id'}
|
||||||
|
response = self.client.post(url, data, format='json', headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data,
|
||||||
|
{'notes':
|
||||||
|
[("Must have one of the following roles: " +
|
||||||
|
"['admin', 'project_owner']")]}
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
def test_new_user_not_my_project_admin(self):
|
||||||
|
"""
|
||||||
|
Can create a user for project that isn't mine if admin.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
project = mock.Mock()
|
||||||
|
project.id = 'test_project_id'
|
||||||
|
project.name = 'test_project'
|
||||||
|
project.roles = []
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {},
|
||||||
|
'projects': {'test_project': project},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/user"
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "admin",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
data = {'email': "test@example.com", 'role': "Member",
|
||||||
|
'project_id': 'test_project_id'}
|
||||||
|
response = self.client.post(url, data, format='json', headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data, {'notes': ['created token']})
|
||||||
|
|
||||||
|
new_token = Token.objects.all()[0]
|
||||||
|
url = "/api_v1/token/" + new_token.token
|
||||||
|
data = {'password': 'testpassword'}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
def test_new_user_not_authenticated(self):
|
||||||
|
"""
|
||||||
|
Can't create a user if unauthenticated.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {},
|
||||||
|
'projects': {},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/user"
|
||||||
|
headers = {}
|
||||||
|
data = {'email': "test@example.com", 'role': "Member",
|
||||||
|
'project_id': 'test_project_id'}
|
||||||
|
response = self.client.post(url, data, format='json', headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data,
|
||||||
|
{'notes':
|
||||||
|
[("Must have one of the following roles: " +
|
||||||
|
"['admin', 'project_owner']")]}
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
@mock.patch('tenant_setup.models.IdentityManager', FakeManager)
|
||||||
|
def test_new_project(self):
|
||||||
|
"""
|
||||||
|
Ensure the new project workflow goes as expected.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {},
|
||||||
|
'projects': {},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/project"
|
||||||
|
data = {'project_name': "test_project", 'email': "test@example.com"}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "admin,Member",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
new_registration = Registration.objects.all()[0]
|
||||||
|
url = "/api_v1/registration/" + new_registration.uuid
|
||||||
|
response = self.client.post(url, {'approved': True}, format='json',
|
||||||
|
headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
new_token = Token.objects.all()[0]
|
||||||
|
url = "/api_v1/token/" + new_token.token
|
||||||
|
data = {'password': 'testpassword'}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
@mock.patch('tenant_setup.models.IdentityManager', FakeManager)
|
||||||
|
def test_new_project_existing(self):
|
||||||
|
"""
|
||||||
|
Test to ensure validation marks actions as invalid
|
||||||
|
if project is already present.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
project = mock.Mock()
|
||||||
|
project.id = 'test_project_id'
|
||||||
|
project.name = 'test_project'
|
||||||
|
project.roles = []
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {},
|
||||||
|
'projects': {'test_project': project},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/user"
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "project_owner,Member",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
url = "/api_v1/project"
|
||||||
|
data = {'project_name': "test_project", 'email': "test@example.com"}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "admin,Member",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
new_registration = Registration.objects.all()[0]
|
||||||
|
url = "/api_v1/registration/" + new_registration.uuid
|
||||||
|
response = self.client.post(url, {'approved': True}, format='json',
|
||||||
|
headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(response.data, {'notes': ['actions invalid']})
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
@mock.patch('tenant_setup.models.IdentityManager', FakeManager)
|
||||||
|
def test_new_project_existing_user(self):
|
||||||
|
"""
|
||||||
|
Project created if not present, existing user attached.
|
||||||
|
No token should be needed.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
user = mock.Mock()
|
||||||
|
user.id = 'user_id'
|
||||||
|
user.name = "test@example.com"
|
||||||
|
user.email = "test@example.com"
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {user.name: user},
|
||||||
|
'projects': {},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/user"
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "project_owner,Member",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
url = "/api_v1/project"
|
||||||
|
data = {'project_name': "test_project", 'email': "test@example.com"}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "admin,Member",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
new_registration = Registration.objects.all()[0]
|
||||||
|
url = "/api_v1/registration/" + new_registration.uuid
|
||||||
|
response = self.client.post(url, {'approved': True}, format='json',
|
||||||
|
headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data,
|
||||||
|
{'notes': 'Registration completed successfully.'}
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
def test_reset_user(self):
|
||||||
|
"""
|
||||||
|
Ensure the reset user workflow goes as expected.
|
||||||
|
Create registration + create token, submit token.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
user = mock.Mock()
|
||||||
|
user.id = 'user_id'
|
||||||
|
user.name = "test@example.com"
|
||||||
|
user.email = "test@example.com"
|
||||||
|
user.password = "test_password"
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {user.name: user},
|
||||||
|
'projects': {},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/reset"
|
||||||
|
data = {'email': "test@example.com"}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data, {'notes': ['created token']})
|
||||||
|
|
||||||
|
new_token = Token.objects.all()[0]
|
||||||
|
url = "/api_v1/token/" + new_token.token
|
||||||
|
data = {'password': 'new_test_password'}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(user.password, 'new_test_password')
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
def test_reset_user_no_existing(self):
|
||||||
|
"""
|
||||||
|
Actions should be invalid.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {},
|
||||||
|
'projects': {},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/reset"
|
||||||
|
data = {'email': "test@example.com"}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(response.data, {'notes': ['actions invalid']})
|
||||||
|
|
||||||
|
def test_no_token_get(self):
|
||||||
|
"""
|
||||||
|
Should be a 404.
|
||||||
|
"""
|
||||||
|
url = "/api_v1/token/e8b3f57f5da64bf3a6bf4f9bbd3a40b5"
|
||||||
|
response = self.client.get(url, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data, {'notes': ['This token does not exist.']})
|
||||||
|
|
||||||
|
def test_no_token_post(self):
|
||||||
|
"""
|
||||||
|
Should be a 404.
|
||||||
|
"""
|
||||||
|
url = "/api_v1/token/e8b3f57f5da64bf3a6bf4f9bbd3a40b5"
|
||||||
|
response = self.client.post(url, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data, {'notes': ['This token does not exist.']})
|
||||||
|
|
||||||
|
def test_no_registration_get(self):
|
||||||
|
"""
|
||||||
|
Should be a 404.
|
||||||
|
"""
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "admin",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
url = "/api_v1/registration/e8b3f57f5da64bf3a6bf4f9bbd3a40b5"
|
||||||
|
response = self.client.get(url, format='json', headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data, {'notes': ['No registration with this id.']})
|
||||||
|
|
||||||
|
def test_no_registration_post(self):
|
||||||
|
"""
|
||||||
|
Should be a 404.
|
||||||
|
"""
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "admin",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
url = "/api_v1/registration/e8b3f57f5da64bf3a6bf4f9bbd3a40b5"
|
||||||
|
response = self.client.post(url, format='json', headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data, {'notes': ['No registration with this id.']})
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
def test_token_expired_post(self):
|
||||||
|
"""
|
||||||
|
Expired token should do nothing, then delete itself.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
user = mock.Mock()
|
||||||
|
user.id = 'user_id'
|
||||||
|
user.name = "test@example.com"
|
||||||
|
user.email = "test@example.com"
|
||||||
|
user.password = "test_password"
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {user.name: user},
|
||||||
|
'projects': {},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/reset"
|
||||||
|
data = {'email': "test@example.com"}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data, {'notes': ['created token']})
|
||||||
|
|
||||||
|
new_token = Token.objects.all()[0]
|
||||||
|
new_token.expires = timezone.now() - timedelta(hours=24)
|
||||||
|
new_token.save()
|
||||||
|
url = "/api_v1/token/" + new_token.token
|
||||||
|
data = {'password': 'new_test_password'}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(response.data, {'notes': ['This token has expired.']})
|
||||||
|
self.assertEqual(0, Token.objects.count())
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
def test_token_expired_get(self):
|
||||||
|
"""
|
||||||
|
Expired token should do nothing, then delete itself.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
|
||||||
|
user = mock.Mock()
|
||||||
|
user.id = 'user_id'
|
||||||
|
user.name = "test@example.com"
|
||||||
|
user.email = "test@example.com"
|
||||||
|
user.password = "test_password"
|
||||||
|
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {user.name: user},
|
||||||
|
'projects': {},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/reset"
|
||||||
|
data = {'email': "test@example.com"}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data, {'notes': ['created token']})
|
||||||
|
|
||||||
|
new_token = Token.objects.all()[0]
|
||||||
|
new_token.expires = timezone.now() - timedelta(hours=24)
|
||||||
|
new_token.save()
|
||||||
|
url = "/api_v1/token/" + new_token.token
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(response.data, {'notes': ['This token has expired.']})
|
||||||
|
self.assertEqual(0, Token.objects.count())
|
||||||
|
|
||||||
|
@mock.patch('base.models.IdentityManager', FakeManager)
|
||||||
|
@mock.patch('tenant_setup.models.IdentityManager', FakeManager)
|
||||||
|
def test_registration_complete(self):
|
||||||
|
"""
|
||||||
|
Can't approve a completed registration.
|
||||||
|
"""
|
||||||
|
global temp_cache
|
||||||
|
temp_cache = {
|
||||||
|
'i': 0,
|
||||||
|
'users': {},
|
||||||
|
'projects': {},
|
||||||
|
'roles': {
|
||||||
|
'Member': 'Member', 'admin': 'admin',
|
||||||
|
'project_owner': 'project_owner'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url = "/api_v1/project"
|
||||||
|
data = {'project_name': "test_project", 'email': "test@example.com"}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'project_name': "test_project",
|
||||||
|
'project_id': "test_project_id",
|
||||||
|
'roles': "admin,Member",
|
||||||
|
'username': "test@example.com",
|
||||||
|
'user_id': "test_user_id",
|
||||||
|
'authenticated': True
|
||||||
|
}
|
||||||
|
new_registration = Registration.objects.all()[0]
|
||||||
|
new_registration.completed = True
|
||||||
|
new_registration.save()
|
||||||
|
url = "/api_v1/registration/" + new_registration.uuid
|
||||||
|
response = self.client.post(url, {'approved': True}, format='json',
|
||||||
|
headers=headers)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data,
|
||||||
|
{'notes': ['This registration has already been completed.']})
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.conf.urls import patterns, url
|
||||||
|
from api_v1 import views
|
||||||
|
|
||||||
|
urlpatterns = patterns(
|
||||||
|
'',
|
||||||
|
# Examples:
|
||||||
|
# url(r'^$', 'user_reg.views.home', name='home'),
|
||||||
|
# url(r'^blog/', include('blog.urls')),
|
||||||
|
|
||||||
|
url(r'^registration/(?P<uuid>\w+)', views.RegistrationDetail.as_view()),
|
||||||
|
url(r'^registration', views.RegistrationList.as_view()),
|
||||||
|
url(r'^project', views.CreateProject.as_view()),
|
||||||
|
url(r'^user', views.AttachUser.as_view()),
|
||||||
|
url(r'^reset', views.ResetPassword.as_view()),
|
||||||
|
url(r'^token/(?P<id>\w+)', views.TokenDetail.as_view()),
|
||||||
|
url(r'^token', views.TokenList.as_view()),
|
||||||
|
)
|
|
@ -0,0 +1,602 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from decorator import decorator
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from models import Registration, Token, Notification
|
||||||
|
from django.utils import timezone
|
||||||
|
from datetime import timedelta
|
||||||
|
from uuid import uuid4
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def admin_or_owner(func, *args, **kwargs):
|
||||||
|
req_roles = {'admin', 'project_owner'}
|
||||||
|
request = args[1]
|
||||||
|
roles = set(request.keystone_user.get('roles', []))
|
||||||
|
|
||||||
|
if roles & req_roles:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return Response({'notes': ["Must have one of the following roles: %s" %
|
||||||
|
list(req_roles)]},
|
||||||
|
403)
|
||||||
|
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def admin(func, *args, **kwargs):
|
||||||
|
request = args[1]
|
||||||
|
roles = request.keystone_user.get('roles', [])
|
||||||
|
if "admin" in roles:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return Response({'notes': ["Must be admin."]},
|
||||||
|
403)
|
||||||
|
|
||||||
|
|
||||||
|
def create_token(registration):
|
||||||
|
# expire needs to be made configurable.
|
||||||
|
expire = timezone.now() + timedelta(hours=24)
|
||||||
|
|
||||||
|
# is this a good way to create tokens?
|
||||||
|
uuid = uuid4().hex
|
||||||
|
token = Token.objects.create(
|
||||||
|
registration=registration,
|
||||||
|
token=uuid,
|
||||||
|
expires=expire
|
||||||
|
)
|
||||||
|
token.save()
|
||||||
|
|
||||||
|
|
||||||
|
def create_notification(registration, notes):
|
||||||
|
notification = Notification.objects.create(
|
||||||
|
registration=registration,
|
||||||
|
notes=notes
|
||||||
|
)
|
||||||
|
notification.save()
|
||||||
|
|
||||||
|
|
||||||
|
class APIViewWithLogger(APIView):
|
||||||
|
"""
|
||||||
|
APIView with a logger.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(APIViewWithLogger, self).__init__(*args, **kwargs)
|
||||||
|
self.logger = getLogger('django.request')
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationList(APIViewWithLogger):
|
||||||
|
|
||||||
|
@admin
|
||||||
|
def get(self, request, format=None):
|
||||||
|
"""A list of dict representations of Notification objects."""
|
||||||
|
notifications = Notification.objects.all()
|
||||||
|
note_list = []
|
||||||
|
for notification in notifications:
|
||||||
|
note_list.append(notification.to_dict())
|
||||||
|
return Response(note_list)
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationList(APIViewWithLogger):
|
||||||
|
|
||||||
|
@admin
|
||||||
|
def get(self, request, format=None):
|
||||||
|
"""A list of dict representations of Registration objects
|
||||||
|
and their related actions."""
|
||||||
|
registrations = Registration.objects.all()
|
||||||
|
reg_list = []
|
||||||
|
for registration in registrations:
|
||||||
|
reg_list.append(registration.to_dict())
|
||||||
|
return Response(reg_list)
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationDetail(APIViewWithLogger):
|
||||||
|
|
||||||
|
@admin
|
||||||
|
def get(self, request, uuid, format=None):
|
||||||
|
"""Dict representation of a Registration object
|
||||||
|
and its related actions."""
|
||||||
|
try:
|
||||||
|
registration = Registration.objects.get(uuid=uuid)
|
||||||
|
except Registration.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{'notes': ['No registration with this id.']},
|
||||||
|
status=404)
|
||||||
|
return Response(registration.to_dict())
|
||||||
|
|
||||||
|
@admin
|
||||||
|
def post(self, request, uuid, format=None):
|
||||||
|
"""Will approve the Registration specified,
|
||||||
|
followed by running the post_approve actions
|
||||||
|
and if valid will setup and create a related token. """
|
||||||
|
try:
|
||||||
|
registration = Registration.objects.get(uuid=uuid)
|
||||||
|
except Registration.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{'notes': ['No registration with this id.']},
|
||||||
|
status=404)
|
||||||
|
|
||||||
|
if request.data.get('approved', False) is True:
|
||||||
|
|
||||||
|
if registration.completed:
|
||||||
|
return Response(
|
||||||
|
{'notes':
|
||||||
|
['This registration has already been completed.']},
|
||||||
|
status=400)
|
||||||
|
registration.approved = True
|
||||||
|
registration.approved_on = timezone.now()
|
||||||
|
registration.save()
|
||||||
|
|
||||||
|
need_token = False
|
||||||
|
valid = True
|
||||||
|
|
||||||
|
actions = []
|
||||||
|
|
||||||
|
for action in registration.actions:
|
||||||
|
act_model = action.get_action()
|
||||||
|
actions.append(act_model)
|
||||||
|
try:
|
||||||
|
act_model.post_approve()
|
||||||
|
except Exception as e:
|
||||||
|
notes = {
|
||||||
|
'errors':
|
||||||
|
[("Error: '%s' while approving registration. " +
|
||||||
|
"See registration itself for details.") % e],
|
||||||
|
'registration': registration.uuid
|
||||||
|
}
|
||||||
|
create_notification(registration, notes)
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
trace = traceback.format_exc()
|
||||||
|
self.logger.critical(("(%s) - Exception escaped! %s\n" +
|
||||||
|
"Trace: \n%s") %
|
||||||
|
(timezone.now(), e, trace))
|
||||||
|
|
||||||
|
return Response(notes, status=500)
|
||||||
|
|
||||||
|
if not action.valid:
|
||||||
|
valid = False
|
||||||
|
if action.need_token:
|
||||||
|
need_token = True
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
if need_token:
|
||||||
|
create_token(registration)
|
||||||
|
return Response({'notes': ['created token']}, status=200)
|
||||||
|
else:
|
||||||
|
for action in actions:
|
||||||
|
try:
|
||||||
|
action.submit({})
|
||||||
|
except Exception as e:
|
||||||
|
notes = {
|
||||||
|
'errors':
|
||||||
|
[("Error: '%s' while submitting " +
|
||||||
|
"registration. See registration " +
|
||||||
|
"itself for details.") % e],
|
||||||
|
'registration': registration.uuid
|
||||||
|
}
|
||||||
|
create_notification(registration, notes)
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
trace = traceback.format_exc()
|
||||||
|
self.logger.critical(("(%s) - Exception escaped!" +
|
||||||
|
" %s\n Trace: \n%s") %
|
||||||
|
(timezone.now(), e, trace))
|
||||||
|
|
||||||
|
return Response(notes, status=500)
|
||||||
|
|
||||||
|
registration.completed = True
|
||||||
|
registration.completed_on = timezone.now()
|
||||||
|
registration.save()
|
||||||
|
return Response(
|
||||||
|
{'notes': "Registration completed successfully."},
|
||||||
|
status=200)
|
||||||
|
return Response({'notes': ['actions invalid']}, status=400)
|
||||||
|
else:
|
||||||
|
return Response({'approved': ["this field is required."]},
|
||||||
|
status=400)
|
||||||
|
|
||||||
|
|
||||||
|
class TokenList(APIViewWithLogger):
|
||||||
|
"""Admin functionality for managing/monitoring tokens."""
|
||||||
|
|
||||||
|
@admin
|
||||||
|
def get(self, request, format=None):
|
||||||
|
"""A list of dict representations of Token objects."""
|
||||||
|
tokens = Token.objects.all()
|
||||||
|
token_list = []
|
||||||
|
for token in tokens:
|
||||||
|
token_list.append(token.to_dict())
|
||||||
|
return Response(token_list)
|
||||||
|
|
||||||
|
|
||||||
|
class TokenDetail(APIViewWithLogger):
|
||||||
|
|
||||||
|
def get(self, request, id, format=None):
|
||||||
|
"""Returns a response with the list of required fields
|
||||||
|
and what actions those go towards."""
|
||||||
|
try:
|
||||||
|
token = Token.objects.get(token=id)
|
||||||
|
except Token.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{'notes': ['This token does not exist.']}, status=404)
|
||||||
|
|
||||||
|
if token.expires < timezone.now():
|
||||||
|
token.delete()
|
||||||
|
return Response({'notes': ['This token has expired.']}, status=400)
|
||||||
|
|
||||||
|
required_fields = []
|
||||||
|
actions = []
|
||||||
|
|
||||||
|
for action in token.registration.actions:
|
||||||
|
action = action.get_action()
|
||||||
|
actions.append(action)
|
||||||
|
for field in action.token_fields:
|
||||||
|
if field not in required_fields:
|
||||||
|
required_fields.append(field)
|
||||||
|
|
||||||
|
return Response({'actions': [unicode(act) for act in actions],
|
||||||
|
'required_fields': required_fields})
|
||||||
|
|
||||||
|
def post(self, request, id, format=None):
|
||||||
|
"""Ensures the required fields are present,
|
||||||
|
will then pass those to the actions via the submit
|
||||||
|
function."""
|
||||||
|
try:
|
||||||
|
token = Token.objects.get(token=id)
|
||||||
|
except Token.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{'notes': ['This token does not exist.']}, status=404)
|
||||||
|
|
||||||
|
if token.expires < timezone.now():
|
||||||
|
token.delete()
|
||||||
|
return Response({'notes': ['This token has expired.']}, status=400)
|
||||||
|
|
||||||
|
required_fields = set()
|
||||||
|
actions = []
|
||||||
|
|
||||||
|
for action in token.registration.actions:
|
||||||
|
action = action.get_action()
|
||||||
|
actions.append(action)
|
||||||
|
for field in action.token_fields:
|
||||||
|
required_fields.add(field)
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
for field in required_fields:
|
||||||
|
try:
|
||||||
|
data[field] = request.data[field]
|
||||||
|
except KeyError:
|
||||||
|
errors[field] = ["This field is required.", ]
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
return Response(errors, status=400)
|
||||||
|
|
||||||
|
for action in actions:
|
||||||
|
try:
|
||||||
|
action.submit(data)
|
||||||
|
except Exception as e:
|
||||||
|
notes = {
|
||||||
|
'errors':
|
||||||
|
[("Error: '%s' while submitting registration. " +
|
||||||
|
"See registration itself for details.") % e],
|
||||||
|
'registration': token.registration.uuid
|
||||||
|
}
|
||||||
|
create_notification(token.registration, notes)
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
trace = traceback.format_exc()
|
||||||
|
self.logger.critical(("(%s) - Exception escaped! %s\n" +
|
||||||
|
"Trace: \n%s") %
|
||||||
|
(timezone.now(), e, trace))
|
||||||
|
|
||||||
|
response_dict = {
|
||||||
|
'errors':
|
||||||
|
["Error: Something went wrong on the server. " +
|
||||||
|
"It will be looked into shortly."]
|
||||||
|
}
|
||||||
|
return Response(response_dict, status=500)
|
||||||
|
|
||||||
|
token.registration.completed = True
|
||||||
|
token.registration.completed_on = timezone.now()
|
||||||
|
token.registration.save()
|
||||||
|
token.delete()
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
{'notes': "Token submitted successfully."},
|
||||||
|
status=200)
|
||||||
|
|
||||||
|
|
||||||
|
class ActionView(APIViewWithLogger):
|
||||||
|
"""Base class for api calls that start a Registration.
|
||||||
|
Until it is moved to settings, 'default_action' is a
|
||||||
|
required hardcoded field."""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
actions = [self.default_action, ]
|
||||||
|
|
||||||
|
actions += settings.API_ACTIONS.get(self.__class__.__name__, [])
|
||||||
|
|
||||||
|
required_fields = []
|
||||||
|
|
||||||
|
for action in actions:
|
||||||
|
action_class, action_serializer = settings.ACTION_CLASSES[action]
|
||||||
|
for field in action_class.required:
|
||||||
|
if field not in required_fields:
|
||||||
|
required_fields.append(field)
|
||||||
|
|
||||||
|
return Response({'actions': actions,
|
||||||
|
'required_fields': required_fields})
|
||||||
|
|
||||||
|
def process_actions(self, request):
|
||||||
|
"""
|
||||||
|
Will ensure the request data contains the required data
|
||||||
|
based on the action serializer, and if present will create
|
||||||
|
a Registration and the linked actions, attaching notes
|
||||||
|
based on running of the the pre_approve validation
|
||||||
|
function on all the actions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
actions = [self.default_action, ]
|
||||||
|
|
||||||
|
actions += settings.API_ACTIONS.get(self.__class__.__name__, [])
|
||||||
|
|
||||||
|
act_list = []
|
||||||
|
|
||||||
|
valid = True
|
||||||
|
for action in actions:
|
||||||
|
action_class, action_serializer = settings.ACTION_CLASSES[action]
|
||||||
|
|
||||||
|
if action_serializer is not None:
|
||||||
|
serializer = action_serializer(data=request.data)
|
||||||
|
else:
|
||||||
|
serializer = None
|
||||||
|
|
||||||
|
act_list.append({
|
||||||
|
'name': action,
|
||||||
|
'action': action_class,
|
||||||
|
'serializer': serializer})
|
||||||
|
|
||||||
|
if serializer is not None and not serializer.is_valid():
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
ip_addr = request.META['REMOTE_ADDR']
|
||||||
|
keystone_user = request.keystone_user
|
||||||
|
|
||||||
|
registration = Registration.objects.create(
|
||||||
|
reg_ip=ip_addr, keystone_user=keystone_user)
|
||||||
|
registration.save()
|
||||||
|
|
||||||
|
for i, act in enumerate(act_list):
|
||||||
|
if act['serializer'] is not None:
|
||||||
|
data = act['serializer'].validated_data
|
||||||
|
else:
|
||||||
|
data = {}
|
||||||
|
action = act['action'](
|
||||||
|
data=data, registration=registration,
|
||||||
|
order=i
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
action.pre_approve()
|
||||||
|
except Exception as e:
|
||||||
|
notes = {
|
||||||
|
'errors':
|
||||||
|
[("Error: '%s' while setting up registration. " +
|
||||||
|
"See registration itself for details.") % e],
|
||||||
|
'registration': registration.uuid
|
||||||
|
}
|
||||||
|
create_notification(registration, notes)
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
trace = traceback.format_exc()
|
||||||
|
self.logger.critical(("(%s) - Exception escaped! %s\n" +
|
||||||
|
"Trace: \n%s") %
|
||||||
|
(timezone.now(), e, trace))
|
||||||
|
|
||||||
|
response_dict = {
|
||||||
|
'errors':
|
||||||
|
["Error: Something went wrong on the server. " +
|
||||||
|
"It will be looked into shortly."]
|
||||||
|
}
|
||||||
|
return response_dict
|
||||||
|
|
||||||
|
return {'registration': registration}
|
||||||
|
else:
|
||||||
|
errors = {}
|
||||||
|
for act in act_list:
|
||||||
|
if act['serializer'] is not None:
|
||||||
|
errors.update(act['serializer'].errors)
|
||||||
|
return {'errors': errors}
|
||||||
|
|
||||||
|
def approve(self, registration):
|
||||||
|
"""
|
||||||
|
Approves the registration and runs the post_approve steps.
|
||||||
|
Will create a token if required, otherwise will run the
|
||||||
|
submit steps.
|
||||||
|
"""
|
||||||
|
registration.approved = True
|
||||||
|
registration.approved_on = timezone.now()
|
||||||
|
registration.save()
|
||||||
|
|
||||||
|
action_models = registration.actions
|
||||||
|
actions = []
|
||||||
|
|
||||||
|
valid = True
|
||||||
|
need_token = False
|
||||||
|
for action in action_models:
|
||||||
|
act = action.get_action()
|
||||||
|
actions.append(act)
|
||||||
|
|
||||||
|
if not act.valid:
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
for action in actions:
|
||||||
|
try:
|
||||||
|
action.post_approve()
|
||||||
|
except Exception as e:
|
||||||
|
notes = {
|
||||||
|
'errors':
|
||||||
|
[("Error: '%s' while approving registration. " +
|
||||||
|
"See registration itself for details.") % e],
|
||||||
|
'registration': registration.uuid
|
||||||
|
}
|
||||||
|
create_notification(registration, notes)
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
trace = traceback.format_exc()
|
||||||
|
self.logger.critical(("(%s) - Exception escaped! %s\n" +
|
||||||
|
"Trace: \n%s") %
|
||||||
|
(timezone.now(), e, trace))
|
||||||
|
|
||||||
|
response_dict = {
|
||||||
|
'errors':
|
||||||
|
["Error: Something went wrong on the server. " +
|
||||||
|
"It will be looked into shortly."]
|
||||||
|
}
|
||||||
|
return Response(response_dict, status=500)
|
||||||
|
|
||||||
|
if not action.valid:
|
||||||
|
valid = False
|
||||||
|
if action.need_token:
|
||||||
|
need_token = True
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
if need_token:
|
||||||
|
create_token(registration)
|
||||||
|
return Response({'notes': ['created token']}, status=200)
|
||||||
|
else:
|
||||||
|
for action in actions:
|
||||||
|
try:
|
||||||
|
action.submit({})
|
||||||
|
except Exception as e:
|
||||||
|
notes = {
|
||||||
|
'errors':
|
||||||
|
[("Error: '%s' while submitting " +
|
||||||
|
"registration. See registration " +
|
||||||
|
"itself for details.") % e],
|
||||||
|
'registration': registration.uuid
|
||||||
|
}
|
||||||
|
create_notification(registration, notes)
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
trace = traceback.format_exc()
|
||||||
|
self.logger.critical(("(%s) - Exception escaped!" +
|
||||||
|
" %s\n Trace: \n%s") %
|
||||||
|
(timezone.now(), e, trace))
|
||||||
|
|
||||||
|
response_dict = {
|
||||||
|
'errors':
|
||||||
|
["Error: Something went wrong on the " +
|
||||||
|
"server. It will be looked into shortly."]
|
||||||
|
}
|
||||||
|
return Response(response_dict, status=500)
|
||||||
|
|
||||||
|
registration.completed = True
|
||||||
|
registration.completed_on = timezone.now()
|
||||||
|
registration.save()
|
||||||
|
return Response(
|
||||||
|
{'notes': "Registration completed successfully."},
|
||||||
|
status=200)
|
||||||
|
return Response({'notes': ['actions invalid']}, status=400)
|
||||||
|
return Response({'notes': ['actions invalid']}, status=400)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateProject(ActionView):
|
||||||
|
|
||||||
|
default_action = "NewProject"
|
||||||
|
|
||||||
|
def post(self, request, format=None):
|
||||||
|
"""
|
||||||
|
Runs internal process_actions and sends back notes or errors.
|
||||||
|
"""
|
||||||
|
self.logger.info("(%s) - Starting new project registration." %
|
||||||
|
timezone.now())
|
||||||
|
processed = self.process_actions(request)
|
||||||
|
|
||||||
|
errors = processed.get('errors', None)
|
||||||
|
if errors:
|
||||||
|
self.logger.info("(%s) - Validation errors with registration." %
|
||||||
|
timezone.now())
|
||||||
|
return Response(errors, status=400)
|
||||||
|
|
||||||
|
notes = {
|
||||||
|
'notes':
|
||||||
|
['New registration for CreateProject.']
|
||||||
|
}
|
||||||
|
create_notification(processed['registration'], notes)
|
||||||
|
self.logger.info("(%s) - Registration created." % timezone.now())
|
||||||
|
return Response({'notes': ['registration created']}, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
class AttachUser(ActionView):
|
||||||
|
|
||||||
|
default_action = 'NewUser'
|
||||||
|
|
||||||
|
@admin_or_owner
|
||||||
|
def get(self, request):
|
||||||
|
return super(AttachUser, self).get(request)
|
||||||
|
|
||||||
|
@admin_or_owner
|
||||||
|
def post(self, request, format=None):
|
||||||
|
"""
|
||||||
|
This endpoint requires either Admin access or the
|
||||||
|
request to come from a project_owner.
|
||||||
|
As such this Registration is considered pre-approved.
|
||||||
|
Runs process_actions, then does the approve and
|
||||||
|
post_approve validation, and creates a Token if valid.
|
||||||
|
"""
|
||||||
|
self.logger.info("(%s) - New AttachUser request." % timezone.now())
|
||||||
|
processed = self.process_actions(request)
|
||||||
|
|
||||||
|
errors = processed.get('errors', None)
|
||||||
|
if errors:
|
||||||
|
self.logger.info("(%s) - Validation errors with registration." %
|
||||||
|
timezone.now())
|
||||||
|
return Response(errors, status=400)
|
||||||
|
|
||||||
|
registration = processed['registration']
|
||||||
|
self.logger.info("(%s) - AutoApproving AttachUser request."
|
||||||
|
% timezone.now())
|
||||||
|
return self.approve(registration)
|
||||||
|
|
||||||
|
|
||||||
|
class ResetPassword(ActionView):
|
||||||
|
|
||||||
|
default_action = 'ResetUser'
|
||||||
|
|
||||||
|
def post(self, request, format=None):
|
||||||
|
"""
|
||||||
|
Unauthenticated endpoint bound to the password reset action.
|
||||||
|
"""
|
||||||
|
self.logger.info("(%s) - New ResetUser request." % timezone.now())
|
||||||
|
processed = self.process_actions(request)
|
||||||
|
|
||||||
|
errors = processed.get('errors', None)
|
||||||
|
if errors:
|
||||||
|
self.logger.info("(%s) - Validation errors with registration." %
|
||||||
|
timezone.now())
|
||||||
|
return Response(errors, status=400)
|
||||||
|
|
||||||
|
registration = processed['registration']
|
||||||
|
self.logger.info("(%s) - AutoApproving Resetuser request."
|
||||||
|
% timezone.now())
|
||||||
|
return self.approve(registration)
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from models import Action
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
admin.site.register(Action)
|
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import django.utils.timezone
|
||||||
|
import jsonfield.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api_v1', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Action',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('action_name', models.CharField(max_length=200)),
|
||||||
|
('action_data', jsonfield.fields.JSONField(default={})),
|
||||||
|
('cache', jsonfield.fields.JSONField(default={})),
|
||||||
|
('state', models.CharField(default=b'default', max_length=200)),
|
||||||
|
('valid', models.BooleanField(default=False)),
|
||||||
|
('need_token', models.BooleanField()),
|
||||||
|
('order', models.IntegerField()),
|
||||||
|
('created', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('registration', models.ForeignKey(to='api_v1.Registration')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,467 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
from user_store import IdentityManager
|
||||||
|
from serializers import (NewUserSerializer, NewProjectSerializer,
|
||||||
|
ResetUserSerializer)
|
||||||
|
from django.conf import settings
|
||||||
|
from jsonfield import JSONField
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
|
||||||
|
class Action(models.Model):
|
||||||
|
"""
|
||||||
|
Database model representation of an action.
|
||||||
|
"""
|
||||||
|
action_name = models.CharField(max_length=200)
|
||||||
|
action_data = JSONField(default={})
|
||||||
|
cache = JSONField(default={})
|
||||||
|
state = models.CharField(max_length=200, default="default")
|
||||||
|
valid = models.BooleanField(default=False)
|
||||||
|
need_token = models.BooleanField()
|
||||||
|
registration = models.ForeignKey('api_v1.Registration')
|
||||||
|
|
||||||
|
order = models.IntegerField()
|
||||||
|
|
||||||
|
created = models.DateTimeField(default=timezone.now)
|
||||||
|
|
||||||
|
def get_action(self):
|
||||||
|
"""Returns self as the appropriate action wrapper type."""
|
||||||
|
data = self.action_data
|
||||||
|
return settings.ACTION_CLASSES[self.action_name][0](
|
||||||
|
data=data, action_model=self)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAction(object):
|
||||||
|
"""
|
||||||
|
Base class for the object wrapping around the database model.
|
||||||
|
Setup to allow multiple action types and different internal logic
|
||||||
|
per type but built from a single database type.
|
||||||
|
- 'required' defines what fields to setup from the data.
|
||||||
|
- 'token_fields' defined which fields are needed by this action
|
||||||
|
at the token stage.
|
||||||
|
|
||||||
|
The Action can do anything it needs at one of the three functions
|
||||||
|
called by the views:
|
||||||
|
- 'pre_approve'
|
||||||
|
- 'post_approve'
|
||||||
|
- 'submit'
|
||||||
|
|
||||||
|
By using 'get_cache' and 'set_cache' they can pass data along which
|
||||||
|
may be needed by the action later. This cache is backed to the database.
|
||||||
|
|
||||||
|
Passing data along to other actions is done via the registration and
|
||||||
|
it's cache, but this is in memory only, so it is only useful during the
|
||||||
|
same action stage ('post_approve', etc.).
|
||||||
|
"""
|
||||||
|
|
||||||
|
required = []
|
||||||
|
|
||||||
|
token_fields = []
|
||||||
|
|
||||||
|
def __init__(self, data, action_model=None, registration=None,
|
||||||
|
order=None):
|
||||||
|
"""
|
||||||
|
Build itself around an existing database model,
|
||||||
|
or build itself and creates a new database model.
|
||||||
|
Sets up required data as fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.logger = getLogger('django.request')
|
||||||
|
|
||||||
|
for field in self.required:
|
||||||
|
field_data = data[field]
|
||||||
|
setattr(self, field, field_data)
|
||||||
|
|
||||||
|
if action_model:
|
||||||
|
self.action = action_model
|
||||||
|
else:
|
||||||
|
if self.token_fields:
|
||||||
|
need_token = True
|
||||||
|
else:
|
||||||
|
need_token = False
|
||||||
|
# make new model and save in db
|
||||||
|
action = Action.objects.create(
|
||||||
|
action_name=self.__class__.__name__,
|
||||||
|
action_data=data,
|
||||||
|
need_token=need_token,
|
||||||
|
registration=registration,
|
||||||
|
order=order
|
||||||
|
)
|
||||||
|
action.save()
|
||||||
|
self.action = action
|
||||||
|
|
||||||
|
@property
|
||||||
|
def valid(self):
|
||||||
|
return self.action.valid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def need_token(self):
|
||||||
|
return self.action.need_token
|
||||||
|
|
||||||
|
def get_cache(self, key):
|
||||||
|
return self.action.cache.get(key, None)
|
||||||
|
|
||||||
|
def set_cache(self, key, value):
|
||||||
|
self.action.cache[key] = value
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
def add_note(self, note):
|
||||||
|
self.logger.info("(%s) - %s" % (timezone.now(), note))
|
||||||
|
note = "%s - (%s)" % (note, timezone.now())
|
||||||
|
self.action.registration.add_action_note(
|
||||||
|
unicode(self), note)
|
||||||
|
|
||||||
|
def pre_approve(self):
|
||||||
|
return self._pre_approve()
|
||||||
|
|
||||||
|
def post_approve(self):
|
||||||
|
return self._post_approve()
|
||||||
|
|
||||||
|
def submit(self, token_data):
|
||||||
|
return self._submit(token_data)
|
||||||
|
|
||||||
|
def _pre_approve(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _post_approve(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _submit(self, token_data):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
|
class UserAction(BaseAction):
|
||||||
|
"""
|
||||||
|
Base action for dealing with users. Removes username if
|
||||||
|
USERNAME_IS_EMAIL and sets email to be username.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
if settings.USERNAME_IS_EMAIL:
|
||||||
|
try:
|
||||||
|
self.required.remove('username')
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
# nothing to remove
|
||||||
|
super(UserAction, self).__init__(*args, **kwargs)
|
||||||
|
self.username = self.email
|
||||||
|
else:
|
||||||
|
super(UserAction, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class NewUser(UserAction):
|
||||||
|
"""
|
||||||
|
Setup a new user with a role on the given project.
|
||||||
|
Creates the user if they don't exist, otherwise
|
||||||
|
if the username and email for the request match the
|
||||||
|
existing one, will simply add the project role.
|
||||||
|
"""
|
||||||
|
|
||||||
|
required = [
|
||||||
|
'username',
|
||||||
|
'email',
|
||||||
|
'project_id',
|
||||||
|
'role'
|
||||||
|
]
|
||||||
|
|
||||||
|
token_fields = ['password']
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
id_manager = IdentityManager()
|
||||||
|
|
||||||
|
user = id_manager.find_user(self.username)
|
||||||
|
|
||||||
|
keystone_user = self.action.registration.keystone_user
|
||||||
|
|
||||||
|
if not ("admin" in keystone_user['roles'] or
|
||||||
|
keystone_user['project_id'] == self.project_id):
|
||||||
|
self.add_note('Project id does not match keystone user project.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
project = id_manager.get_project(self.project_id)
|
||||||
|
|
||||||
|
if not project:
|
||||||
|
self.add_note('Project does not exist.')
|
||||||
|
valid = False
|
||||||
|
else:
|
||||||
|
if user:
|
||||||
|
if user.email == self.email:
|
||||||
|
valid = True
|
||||||
|
self.action.need_token = False
|
||||||
|
self.action.state = "existing"
|
||||||
|
self.add_note('Existing user with matching email.')
|
||||||
|
else:
|
||||||
|
valid = False
|
||||||
|
self.add_note('Existing user with non-matching email.')
|
||||||
|
else:
|
||||||
|
valid = True
|
||||||
|
self.add_note('No user present with username')
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
def _pre_approve(self):
|
||||||
|
self.action.valid = self._validate()
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
def _post_approve(self):
|
||||||
|
self.action.valid = self._validate()
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
def _submit(self, token_data):
|
||||||
|
self.action.valid = self._validate()
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
if self.valid:
|
||||||
|
id_manager = IdentityManager()
|
||||||
|
|
||||||
|
if self.action.state == "default":
|
||||||
|
try:
|
||||||
|
user = id_manager.create_user(
|
||||||
|
name=self.username, password=token_data['password'],
|
||||||
|
email=self.email, project_id=self.project_id)
|
||||||
|
role = id_manager.find_role(self.role)
|
||||||
|
id_manager.add_user_role(user, role, self.project_id)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while creating user: %s with role: " %
|
||||||
|
(e, self.username, self.role))
|
||||||
|
raise
|
||||||
|
|
||||||
|
self.add_note(
|
||||||
|
'User %s has been created, with role %s in project %s.'
|
||||||
|
% (self.username, self.role, self.project_id))
|
||||||
|
elif self.action.state == "existing":
|
||||||
|
try:
|
||||||
|
user = id_manager.find_user(self.username)
|
||||||
|
role = id_manager.find_role(self.role)
|
||||||
|
id_manager.add_user_role(user, role, self.project_id)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while attaching user: %s with role: " %
|
||||||
|
(e, self.username, self.role))
|
||||||
|
raise
|
||||||
|
|
||||||
|
self.add_note(
|
||||||
|
'Existing user %s has been given role %s in project %s.'
|
||||||
|
% (self.username, self.role, self.project_id))
|
||||||
|
|
||||||
|
|
||||||
|
class NewProject(UserAction):
|
||||||
|
"""
|
||||||
|
Similar functionality as the NewUser action,
|
||||||
|
but will create the project if valid. Will setup
|
||||||
|
the user (existing or new) with the 'default_role'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
required = [
|
||||||
|
'project_name',
|
||||||
|
'username',
|
||||||
|
'email'
|
||||||
|
]
|
||||||
|
|
||||||
|
default_roles = ["Member", "project_owner"]
|
||||||
|
|
||||||
|
token_fields = ['password']
|
||||||
|
|
||||||
|
def _validate_project(self):
|
||||||
|
id_manager = IdentityManager()
|
||||||
|
|
||||||
|
project = id_manager.find_project(self.project_name)
|
||||||
|
|
||||||
|
valid = self._validate_user(id_manager)
|
||||||
|
|
||||||
|
if project:
|
||||||
|
valid = False
|
||||||
|
self.add_note("Existing project with name '%s'." %
|
||||||
|
self.project_name)
|
||||||
|
else:
|
||||||
|
self.add_note("No existing project with name '%s'." %
|
||||||
|
self.project_name)
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
def _validate_user(self, id_manager):
|
||||||
|
user = id_manager.find_user(self.username)
|
||||||
|
|
||||||
|
if user:
|
||||||
|
if user.email == self.email:
|
||||||
|
valid = True
|
||||||
|
self.action.state = "existing"
|
||||||
|
self.action.need_token = False
|
||||||
|
self.add_note("Existing user '%s' with matching email." %
|
||||||
|
self.email)
|
||||||
|
else:
|
||||||
|
valid = False
|
||||||
|
self.add_note("Existing user '%s' with non-matching email." %
|
||||||
|
self.username)
|
||||||
|
else:
|
||||||
|
valid = True
|
||||||
|
self.add_note("No user present with username '%s'." %
|
||||||
|
self.username)
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
def _pre_approve(self):
|
||||||
|
self.action.valid = self._validate_project()
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
def _post_approve(self):
|
||||||
|
project_id = self.get_cache('project_id')
|
||||||
|
if project_id:
|
||||||
|
self.action.registration.cache['project_id'] = project_id
|
||||||
|
self.add_note("Project already created.")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.action.valid = self._validate_project()
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
if self.valid:
|
||||||
|
id_manager = IdentityManager()
|
||||||
|
try:
|
||||||
|
project = id_manager.create_project(self.project_name)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while creating project: %s" %
|
||||||
|
(e, self.project_name))
|
||||||
|
raise
|
||||||
|
# put project_id into action cache:
|
||||||
|
self.action.registration.cache['project_id'] = project.id
|
||||||
|
self.set_cache('project_id', project.id)
|
||||||
|
self.add_note("New project '%s' created." % self.project_name)
|
||||||
|
|
||||||
|
def _submit(self, token_data):
|
||||||
|
id_manager = IdentityManager()
|
||||||
|
|
||||||
|
self.action.valid = self._validate_user(id_manager)
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
if self.valid:
|
||||||
|
|
||||||
|
project_id = self.get_cache('project_id')
|
||||||
|
self.action.registration.cache['project_id'] = project_id
|
||||||
|
|
||||||
|
project = id_manager.get_project(project_id)
|
||||||
|
|
||||||
|
if self.action.state == "default":
|
||||||
|
try:
|
||||||
|
user = id_manager.create_user(
|
||||||
|
name=self.username, password=token_data['password'],
|
||||||
|
email=self.email, project_id=project.id)
|
||||||
|
|
||||||
|
for role in self.default_roles:
|
||||||
|
ks_role = id_manager.find_role(role)
|
||||||
|
id_manager.add_user_role(user, ks_role, project.id)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while creating user: %s with roles: %s" %
|
||||||
|
(e, self.username, self.default_roles))
|
||||||
|
raise
|
||||||
|
|
||||||
|
self.add_note(
|
||||||
|
"New user '%s' created for project %s with roles: %s" %
|
||||||
|
(self.username, self.project_name, self.default_roles))
|
||||||
|
elif self.action.state == "existing":
|
||||||
|
user = id_manager.find_user(self.username)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for role in self.default_roles:
|
||||||
|
ks_role = id_manager.find_role(role)
|
||||||
|
id_manager.add_user_role(user, ks_role, project.id)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while attaching user: %s with roles: %s" %
|
||||||
|
(e, self.username, self.default_roles))
|
||||||
|
raise
|
||||||
|
|
||||||
|
self.add_note(("Existing user '%s' attached to project %s" +
|
||||||
|
" with roles: %s")
|
||||||
|
% (self.username, self.project_name,
|
||||||
|
self.default_roles))
|
||||||
|
|
||||||
|
|
||||||
|
class ResetUser(UserAction):
|
||||||
|
"""
|
||||||
|
Simple action to reset a password for a given user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
username = models.CharField(max_length=200)
|
||||||
|
email = models.EmailField()
|
||||||
|
|
||||||
|
required = [
|
||||||
|
'username',
|
||||||
|
'email'
|
||||||
|
]
|
||||||
|
|
||||||
|
token_fields = ['password']
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
id_manager = IdentityManager()
|
||||||
|
|
||||||
|
user = id_manager.find_user(self.username)
|
||||||
|
|
||||||
|
if user:
|
||||||
|
if user.email == self.email:
|
||||||
|
valid = True
|
||||||
|
self.add_note('Existing user with matching email.')
|
||||||
|
else:
|
||||||
|
valid = False
|
||||||
|
self.add_note('Existing user with non-matching email.')
|
||||||
|
else:
|
||||||
|
valid = False
|
||||||
|
self.add_note('No user present with username')
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
def _pre_approve(self):
|
||||||
|
self.action.valid = self._validate()
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
def _post_approve(self):
|
||||||
|
self.action.valid = self._validate()
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
def _submit(self, token_data):
|
||||||
|
self.action.valid = self._validate()
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
if self.valid:
|
||||||
|
id_manager = IdentityManager()
|
||||||
|
|
||||||
|
user = id_manager.find_user(self.username)
|
||||||
|
try:
|
||||||
|
id_manager.update_user_password(user, token_data['password'])
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while changing password for user: %s" %
|
||||||
|
(e, self.username))
|
||||||
|
raise
|
||||||
|
self.add_note('User %s password has been changed.' % self.username)
|
||||||
|
|
||||||
|
|
||||||
|
# A dict of tuples in the format: (<ActionClass>, <ActionSerializer>)
|
||||||
|
action_classes = {
|
||||||
|
'NewUser': (NewUser, NewUserSerializer),
|
||||||
|
'NewProject': (NewProject, NewProjectSerializer),
|
||||||
|
'ResetUser': (ResetUser, ResetUserSerializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
# setup action classes and serializers for global access
|
||||||
|
settings.ACTION_CLASSES.update(action_classes)
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from keystoneclient.v2_0 import client
|
||||||
|
from neutronclient.v2_0 import client as neutron_client
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
def get_keystoneclient():
|
||||||
|
# try:
|
||||||
|
# return clients['keystone']
|
||||||
|
# except KeyError:
|
||||||
|
# auth = v3.Password(
|
||||||
|
# auth_url='http://10.0.2.15:5000/v3',
|
||||||
|
# user_id='admin',
|
||||||
|
# password='openstack',
|
||||||
|
# project_name='admin'
|
||||||
|
# )
|
||||||
|
# sess = session.Session(auth=auth)
|
||||||
|
# keystone = KeystoneClient.Client(session=sess)
|
||||||
|
# clients['keystone'] = keystone
|
||||||
|
auth = client.Client(
|
||||||
|
username=settings.KEYSTONE['username'],
|
||||||
|
password=settings.KEYSTONE['password'],
|
||||||
|
tenant_name=settings.KEYSTONE['project_name'],
|
||||||
|
auth_url=settings.KEYSTONE['auth_url']
|
||||||
|
)
|
||||||
|
return auth
|
||||||
|
|
||||||
|
|
||||||
|
def get_neutronclient():
|
||||||
|
neutron = neutron_client.Client(
|
||||||
|
username=settings.KEYSTONE['username'],
|
||||||
|
password=settings.KEYSTONE['password'],
|
||||||
|
tenant_name=settings.KEYSTONE['project_name'],
|
||||||
|
auth_url=settings.KEYSTONE['auth_url'])
|
||||||
|
return neutron
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUserSerializer(serializers.Serializer):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(BaseUserSerializer, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if settings.USERNAME_IS_EMAIL:
|
||||||
|
self.fields.pop('username')
|
||||||
|
|
||||||
|
|
||||||
|
class NewUserSerializer(BaseUserSerializer):
|
||||||
|
username = serializers.CharField(max_length=200)
|
||||||
|
email = serializers.EmailField()
|
||||||
|
project_id = serializers.CharField(max_length=200)
|
||||||
|
|
||||||
|
role_options = (('project_mod', 'Project Owner (can add new users)'),
|
||||||
|
('Member', "Project Member (can't add new users)"))
|
||||||
|
role = serializers.ChoiceField(choices=role_options)
|
||||||
|
|
||||||
|
|
||||||
|
class NewProjectSerializer(BaseUserSerializer):
|
||||||
|
project_name = serializers.CharField(max_length=200)
|
||||||
|
username = serializers.CharField(max_length=200)
|
||||||
|
email = serializers.EmailField()
|
||||||
|
|
||||||
|
|
||||||
|
class ResetUserSerializer(BaseUserSerializer):
|
||||||
|
username = serializers.CharField(max_length=200)
|
||||||
|
email = serializers.EmailField()
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
|
@ -0,0 +1,80 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from openstack_clients import get_keystoneclient
|
||||||
|
from keystoneclient.openstack.common.apiclient import (
|
||||||
|
exceptions as ks_exceptions
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IdentityManager(object):
|
||||||
|
"""
|
||||||
|
A wrapper object for the Keystone Client. Mainly setup as
|
||||||
|
such for easier testing, but also so it can be replaced
|
||||||
|
later with an LDAP + Keystone Client variant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.ks_client = get_keystoneclient()
|
||||||
|
|
||||||
|
def find_user(self, name):
|
||||||
|
try:
|
||||||
|
user = self.ks_client.users.find(name=name)
|
||||||
|
except ks_exceptions.NotFound:
|
||||||
|
user = None
|
||||||
|
return user
|
||||||
|
|
||||||
|
def get_user(self, user_id):
|
||||||
|
try:
|
||||||
|
user = self.ks_client.users.get(user_id)
|
||||||
|
except ks_exceptions.NotFound:
|
||||||
|
user = None
|
||||||
|
return user
|
||||||
|
|
||||||
|
def create_user(self, name, password, email, project_id):
|
||||||
|
user = self.ks_client.users.create(
|
||||||
|
name=name, password=password,
|
||||||
|
email=email, tenant_id=project_id)
|
||||||
|
return user
|
||||||
|
|
||||||
|
def update_user_password(self, user, password):
|
||||||
|
self.ks_client.users.update_password(user, password)
|
||||||
|
|
||||||
|
def find_role(self, name):
|
||||||
|
try:
|
||||||
|
role = self.ks_client.roles.find(name=name)
|
||||||
|
except ks_exceptions.NotFound:
|
||||||
|
role = None
|
||||||
|
return role
|
||||||
|
|
||||||
|
def add_user_role(self, user, role, project_id):
|
||||||
|
self.ks_client.roles.add_user_role(user, role, project_id)
|
||||||
|
|
||||||
|
def find_project(self, project_name):
|
||||||
|
try:
|
||||||
|
project = self.ks_client.tenants.find(name=project_name)
|
||||||
|
except ks_exceptions.NotFound:
|
||||||
|
project = None
|
||||||
|
return project
|
||||||
|
|
||||||
|
def get_project(self, project_id):
|
||||||
|
try:
|
||||||
|
project = self.ks_client.tenants.get(project_id)
|
||||||
|
except ks_exceptions.NotFound:
|
||||||
|
project = None
|
||||||
|
return project
|
||||||
|
|
||||||
|
def create_project(self, project_name):
|
||||||
|
project = self.ks_client.tenants.create(project_name)
|
||||||
|
return project
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "user_reg.settings")
|
||||||
|
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
|
execute_from_command_line(sys.argv)
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
|
@ -0,0 +1,36 @@
|
||||||
|
from base.models import BaseAction
|
||||||
|
from serializers import NewClientSerializer
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class NewClient(BaseAction):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
required = [
|
||||||
|
'business_name',
|
||||||
|
'billing_address',
|
||||||
|
'billing_phone',
|
||||||
|
'billing_email'
|
||||||
|
]
|
||||||
|
|
||||||
|
def _pre_approve(self):
|
||||||
|
self.action.valid = True
|
||||||
|
self.action.need_token = False
|
||||||
|
self.action.save()
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _post_approve(self):
|
||||||
|
self.action.valid = True
|
||||||
|
self.action.need_token = False
|
||||||
|
self.action.save()
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _submit(self, token_data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
action_classes = {
|
||||||
|
'NewClient': (NewClient, NewClientSerializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.ACTION_CLASSES.update(action_classes)
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
|
@ -0,0 +1,6 @@
|
||||||
|
Django==1.7.3
|
||||||
|
decorator==3.4.0
|
||||||
|
djangorestframework==3.0.3
|
||||||
|
keystonemiddleware==1.3.1
|
||||||
|
python-keystoneclient==1.0.0
|
||||||
|
jsonfield==1.0.2
|
|
@ -0,0 +1,210 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from base.models import BaseAction
|
||||||
|
from serializers import DefaultProjectResourcesSerializer
|
||||||
|
from django.conf import settings
|
||||||
|
from base.user_store import IdentityManager
|
||||||
|
from base import openstack_clients
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultProjectResources(BaseAction):
|
||||||
|
"""
|
||||||
|
This action will setup all required basic networking
|
||||||
|
resources so that a new user can launch instances
|
||||||
|
right away.
|
||||||
|
"""
|
||||||
|
|
||||||
|
required = [
|
||||||
|
'setup_resources'
|
||||||
|
]
|
||||||
|
|
||||||
|
defaults = settings.NETWORK_DEFAULTS[settings.DEFAULT_REGION]
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
|
||||||
|
project_id = self.action.registration.cache.get('project_id', None)
|
||||||
|
|
||||||
|
valid = False
|
||||||
|
if project_id:
|
||||||
|
valid = True
|
||||||
|
self.add_note('project_id given: %s' % project_id)
|
||||||
|
else:
|
||||||
|
self.add_note('No project_id given.')
|
||||||
|
return valid
|
||||||
|
|
||||||
|
def _setup_resources(self):
|
||||||
|
neutron = openstack_clients.get_neutronclient()
|
||||||
|
|
||||||
|
project_id = self.action.registration.cache['project_id']
|
||||||
|
|
||||||
|
if not self.get_cache('network_id'):
|
||||||
|
try:
|
||||||
|
network_body = {
|
||||||
|
"network": {
|
||||||
|
"name": self.defaults['network_name'],
|
||||||
|
'tenant_id': project_id,
|
||||||
|
"admin_state_up": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
network = neutron.create_network(body=network_body)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while creating network: %s" %
|
||||||
|
(e, self.defaults['network_name']))
|
||||||
|
raise
|
||||||
|
self.set_cache('network_id', network['network']['id'])
|
||||||
|
self.add_note("Network %s created for project %s" %
|
||||||
|
(self.defaults['network_name'],
|
||||||
|
self.action.registration.cache['project_id']))
|
||||||
|
else:
|
||||||
|
self.add_note("Network %s already created for project %s" %
|
||||||
|
(self.defaults['network_name'],
|
||||||
|
self.action.registration.cache['project_id']))
|
||||||
|
|
||||||
|
if not self.get_cache('subnet_id'):
|
||||||
|
try:
|
||||||
|
subnet_body = {
|
||||||
|
"subnet": {
|
||||||
|
"network_id": self.get_cache('network_id'),
|
||||||
|
"ip_version": 4,
|
||||||
|
'tenant_id': project_id,
|
||||||
|
'dns_nameservers': self.defaults['DNS_NAMESERVERS'],
|
||||||
|
"cidr": self.defaults['SUBNET_CIDR']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subnet = neutron.create_subnet(body=subnet_body)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while creating subnet" % e)
|
||||||
|
raise
|
||||||
|
self.set_cache('subnet_id', subnet['subnet']['id'])
|
||||||
|
self.add_note("Subnet created for network %s" %
|
||||||
|
self.defaults['network_name'])
|
||||||
|
else:
|
||||||
|
self.add_note("Subnet already created for network %s" %
|
||||||
|
self.defaults['network_name'])
|
||||||
|
|
||||||
|
if not self.get_cache('router_id'):
|
||||||
|
try:
|
||||||
|
router_body = {
|
||||||
|
"router": {
|
||||||
|
"name": self.defaults['router_name'],
|
||||||
|
"external_gateway_info": {
|
||||||
|
"network_id": self.defaults['public_network']
|
||||||
|
},
|
||||||
|
'tenant_id': project_id,
|
||||||
|
"admin_state_up": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router = neutron.create_router(body=router_body)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while creating router: %s" %
|
||||||
|
(e, self.defaults['router_name']))
|
||||||
|
raise
|
||||||
|
self.set_cache('router_id', router['router']['id'])
|
||||||
|
self.add_note("Router created for project %s" %
|
||||||
|
self.action.registration.cache['project_id'])
|
||||||
|
else:
|
||||||
|
self.add_note("Router already created for project %s" %
|
||||||
|
self.action.registration.cache['project_id'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
interface_body = {
|
||||||
|
"subnet_id": self.get_cache('subnet_id')
|
||||||
|
}
|
||||||
|
neutron.add_interface_router(self.get_cache('router_id'),
|
||||||
|
body=interface_body)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while attaching interface" % e)
|
||||||
|
raise
|
||||||
|
self.add_note("Interface added to router for subnet")
|
||||||
|
|
||||||
|
def _pre_approve(self):
|
||||||
|
# Not exactly valid, but not exactly invalid.
|
||||||
|
self.action.valid = True
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
def _post_approve(self):
|
||||||
|
self.action.valid = self._validate()
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
if self.setup_resources and self.valid:
|
||||||
|
self._setup_resources()
|
||||||
|
|
||||||
|
def _submit(self, token_data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AddAdminToProject(BaseAction):
|
||||||
|
"""
|
||||||
|
Action to add 'admin' user to project for
|
||||||
|
monitoring purposes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _validate(self):
|
||||||
|
|
||||||
|
project_id = self.action.registration.cache.get('project_id', None)
|
||||||
|
|
||||||
|
valid = False
|
||||||
|
if project_id:
|
||||||
|
valid = True
|
||||||
|
self.add_note('project_id given: %s' % project_id)
|
||||||
|
else:
|
||||||
|
self.add_note('No project_id given.')
|
||||||
|
return valid
|
||||||
|
|
||||||
|
def _pre_approve(self):
|
||||||
|
# Not yet exactly valid, but not exactly invalid.
|
||||||
|
self.action.valid = True
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
def _post_approve(self):
|
||||||
|
self.action.valid = self._validate()
|
||||||
|
self.action.save()
|
||||||
|
|
||||||
|
if self.valid and not self.action.state == "completed":
|
||||||
|
id_manager = IdentityManager()
|
||||||
|
|
||||||
|
project = id_manager.get_project(
|
||||||
|
self.action.registration.cache['project_id'])
|
||||||
|
try:
|
||||||
|
user = id_manager.find_user(name="admin")
|
||||||
|
role = id_manager.find_role(name="admin")
|
||||||
|
id_manager.add_user_role(user, role, project.id)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_note(
|
||||||
|
"Error: '%s' while adding admin to project: %s" %
|
||||||
|
(e, project.id))
|
||||||
|
raise
|
||||||
|
self.action.state = "completed"
|
||||||
|
self.action.save()
|
||||||
|
self.add_note(
|
||||||
|
'Admin has been added to %s.' %
|
||||||
|
self.action.registration.cache['project_id'])
|
||||||
|
|
||||||
|
def _submit(self, token_data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
action_classes = {
|
||||||
|
'DefaultProjectResources': (DefaultProjectResources,
|
||||||
|
DefaultProjectResourcesSerializer),
|
||||||
|
'AddAdminToProject': (AddAdminToProject, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
settings.ACTION_CLASSES.update(action_classes)
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultProjectResourcesSerializer(serializers.Serializer):
|
||||||
|
setup_resources = serializers.BooleanField(default=False)
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
|
@ -0,0 +1,84 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
from time import time
|
||||||
|
from logging import getLogger
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
class KeystoneHeaderUnwrapper(object):
|
||||||
|
"""
|
||||||
|
Middleware to build an easy to use dict of important data from
|
||||||
|
what the keystone wsgi middleware gives us.
|
||||||
|
"""
|
||||||
|
def process_request(self, request):
|
||||||
|
try:
|
||||||
|
token_data = {
|
||||||
|
'project_name': request.META['HTTP_X_PROJECT_NAME'],
|
||||||
|
'project_id': request.META['HTTP_X_PROJECT_ID'],
|
||||||
|
'roles': request.META['HTTP_X_ROLES'].split(','),
|
||||||
|
'username': request.META['HTTP_X_USER_NAME'],
|
||||||
|
'user_id': request.META['HTTP_X_USER_ID'],
|
||||||
|
'authenticated': request.META['HTTP_X_IDENTITY_STATUS']
|
||||||
|
}
|
||||||
|
except KeyError:
|
||||||
|
token_data = {}
|
||||||
|
request.keystone_user = token_data
|
||||||
|
|
||||||
|
|
||||||
|
class TestingHeaderUnwrapper(object):
|
||||||
|
"""
|
||||||
|
Replacement for the KeystoneHeaderUnwrapper for testing purposes.
|
||||||
|
"""
|
||||||
|
def process_request(self, request):
|
||||||
|
try:
|
||||||
|
token_data = {
|
||||||
|
'project_name': request.META['headers']['project_name'],
|
||||||
|
'project_id': request.META['headers']['project_id'],
|
||||||
|
'roles': request.META['headers']['roles'].split(','),
|
||||||
|
'username': request.META['headers']['username'],
|
||||||
|
'user_id': request.META['headers']['user_id'],
|
||||||
|
'authenticated': request.META['headers']['authenticated']
|
||||||
|
}
|
||||||
|
except KeyError:
|
||||||
|
token_data = {}
|
||||||
|
request.keystone_user = token_data
|
||||||
|
|
||||||
|
|
||||||
|
class RequestLoggingMiddleware(object):
|
||||||
|
"""
|
||||||
|
Middleware to log the requests and responses.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = getLogger('django.request')
|
||||||
|
|
||||||
|
def process_request(self, request):
|
||||||
|
self.logger.info(
|
||||||
|
'(%s) - <%s> %s [%s]',
|
||||||
|
timezone.now(),
|
||||||
|
request.method,
|
||||||
|
request.META['REMOTE_ADDR'],
|
||||||
|
request.get_full_path()
|
||||||
|
)
|
||||||
|
request.timer = time()
|
||||||
|
|
||||||
|
def process_response(self, request, response):
|
||||||
|
self.logger.info(
|
||||||
|
'(%s) - <%s> [%s] - (%.1fs)',
|
||||||
|
timezone.now(),
|
||||||
|
response.status_code,
|
||||||
|
request.get_full_path(),
|
||||||
|
time() - request.timer
|
||||||
|
)
|
||||||
|
return response
|
|
@ -0,0 +1,170 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Django settings for user_reg project.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.7/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/1.7/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '+er!!4olta#17a=n%uotcazg2ncpl==yjog%1*o-(cr%zys-)!'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
TEMPLATE_DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'rest_framework',
|
||||||
|
'base',
|
||||||
|
'api_v1',
|
||||||
|
'tenant_setup',
|
||||||
|
)
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = (
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'user_reg.middleware.KeystoneHeaderUnwrapper',
|
||||||
|
'user_reg.middleware.RequestLoggingMiddleware'
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'test' in sys.argv:
|
||||||
|
# modify MIDDLEWARE_CLASSES
|
||||||
|
MIDDLEWARE_CLASSES = list(MIDDLEWARE_CLASSES)
|
||||||
|
MIDDLEWARE_CLASSES.remove('user_reg.middleware.KeystoneHeaderUnwrapper')
|
||||||
|
MIDDLEWARE_CLASSES.append('user_reg.middleware.TestingHeaderUnwrapper')
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'user_reg.urls'
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'user_reg.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/1.7/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/1.7/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'handlers': {
|
||||||
|
'file': {
|
||||||
|
'level': 'INFO',
|
||||||
|
'class': 'logging.FileHandler',
|
||||||
|
'filename': 'reg_log.log',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'django': {
|
||||||
|
'handlers': ['file'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': True,
|
||||||
|
},
|
||||||
|
'keystonemiddleware': {
|
||||||
|
'handlers': ['file'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# App apecific settings:
|
||||||
|
|
||||||
|
USERNAME_IS_EMAIL = True
|
||||||
|
|
||||||
|
# Keystone admin credentials:
|
||||||
|
KEYSTONE = {
|
||||||
|
'username': 'admin',
|
||||||
|
'password': 'openstack',
|
||||||
|
'project_name': 'admin',
|
||||||
|
'auth_url': "http://localhost:5000/v2.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_REGION = "REGION_ONE"
|
||||||
|
|
||||||
|
NETWORK_DEFAULTS = {
|
||||||
|
"REGION_ONE": {
|
||||||
|
'network_name': 'somenetwork',
|
||||||
|
'subnet_name': 'somesubnet',
|
||||||
|
'router_name': 'somerouter',
|
||||||
|
# this depends on region and needs to be pulled from somewhere:
|
||||||
|
'public_network': '1eb739bb-607d-4a34-a590-9c15d03ccbe7',
|
||||||
|
'DNS_NAMESERVERS': ['193.168.1.2',
|
||||||
|
'193.168.1.3'],
|
||||||
|
'SUBNET_CIDR': '192.168.1.0/24'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# the order of the actions matters. These will run after the default action,
|
||||||
|
# in the given order.
|
||||||
|
API_ACTIONS = {'CreateProject': ['AddAdminToProject',
|
||||||
|
'DefaultProjectResources']}
|
||||||
|
|
||||||
|
# This is populated from the various model modules at startup:
|
||||||
|
ACTION_CLASSES = {}
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from django.conf.urls import patterns, include, url
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
urlpatterns = patterns(
|
||||||
|
'',
|
||||||
|
# Examples:
|
||||||
|
# url(r'^$', 'user_reg.views.home', name='home'),
|
||||||
|
# url(r'^blog/', include('blog.urls')),
|
||||||
|
|
||||||
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
|
url(r'^api_v1/', include('api_v1.urls')),
|
||||||
|
)
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Copyright (C) 2015 Catalyst IT Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
WSGI config for user_reg project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "user_reg.settings")
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
application = get_wsgi_application()
|
||||||
|
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from urlparse import urlparse
|
||||||
|
from keystonemiddleware.auth_token import AuthProtocol
|
||||||
|
|
||||||
|
identity_url = urlparse(settings.KEYSTONE['auth_url'])
|
||||||
|
conf = {
|
||||||
|
'admin_user': settings.KEYSTONE['username'],
|
||||||
|
'admin_password': settings.KEYSTONE['password'],
|
||||||
|
'admin_tenant_name': settings.KEYSTONE['project_name'],
|
||||||
|
'auth_host': identity_url.hostname,
|
||||||
|
'auth_port': identity_url.port,
|
||||||
|
'auth_protocol': identity_url.scheme,
|
||||||
|
'delay_auth_decision': True,
|
||||||
|
'include_service_catalog': False
|
||||||
|
}
|
||||||
|
application = AuthProtocol(application, conf)
|
Loading…
Reference in New Issue