Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Gutyán Gábor
/
circlestack
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
4c29a7bb
authored
May 31, 2018
by
Szabolcs Gelencser
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement template share with users
parent
0ec3899c
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
253 additions
and
275 deletions
+253
-275
circle/dashboard/forms.py
+35
-34
circle/dashboard/templates/dashboard/_manage_access.html
+12
-26
circle/dashboard/templates/dashboard/template-edit.html
+4
-58
circle/dashboard/urls.py
+4
-3
circle/dashboard/views/autocomplete.py
+5
-6
circle/dashboard/views/template.py
+55
-3
circle/dashboard/views/util.py
+127
-143
circle/openstack_api/keystone.py
+2
-2
circle/vm/models/instance.py
+9
-0
No files found.
circle/dashboard/forms.py
View file @
4c29a7bb
...
@@ -433,11 +433,17 @@ class TemplateForm(forms.ModelForm):
...
@@ -433,11 +433,17 @@ class TemplateForm(forms.ModelForm):
# }))
# }))
name
=
forms
.
TextInput
()
name
=
forms
.
TextInput
()
flavor
=
forms
.
ChoiceField
([
'a'
,
'b'
,
'c'
])
#
flavor = forms.ChoiceField(['a','b','c'])
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
user
=
kwargs
.
pop
(
"user"
,
None
)
self
.
user
=
kwargs
.
pop
(
"user"
,
None
)
super
(
TemplateForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
super
(
TemplateForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
allowed_fields
=
(
'name'
,
# 'lease',
)
#
#
# self.fields['networks'].queryset = ()#Vlan.get_objects_with_level('user', self.user)
# self.fields['networks'].queryset = ()#Vlan.get_objects_with_level('user', self.user)
#
#
...
@@ -487,34 +493,34 @@ class TemplateForm(forms.ModelForm):
...
@@ -487,34 +493,34 @@ class TemplateForm(forms.ModelForm):
def
clean_max_ram_size
(
self
):
def
clean_max_ram_size
(
self
):
return
self
.
cleaned_data
.
get
(
"ram_size"
,
0
)
return
self
.
cleaned_data
.
get
(
"ram_size"
,
0
)
def
_clean_fields
(
self
):
#
def _clean_fields(self):
try
:
#
try:
old
=
InstanceTemplate
.
objects
.
get
(
pk
=
self
.
instance
.
pk
)
#
old = InstanceTemplate.objects.get(pk=self.instance.pk)
except
InstanceTemplate
.
DoesNotExist
:
#
except InstanceTemplate.DoesNotExist:
old
=
None
#
old = None
for
name
,
field
in
self
.
fields
.
items
():
#
for name, field in self.fields.items():
if
name
in
self
.
allowed_fields
:
#
if name in self.allowed_fields:
value
=
field
.
widget
.
value_from_datadict
(
#
value = field.widget.value_from_datadict(
self
.
data
,
self
.
files
,
self
.
add_prefix
(
name
))
#
self.data, self.files, self.add_prefix(name))
try
:
#
try:
value
=
field
.
clean
(
value
)
#
value = field.clean(value)
self
.
cleaned_data
[
name
]
=
value
#
self.cleaned_data[name] = value
if
hasattr
(
self
,
'clean_
%
s'
%
name
):
#
if hasattr(self, 'clean_%s' % name):
value
=
getattr
(
self
,
'clean_
%
s'
%
name
)()
#
value = getattr(self, 'clean_%s' % name)()
self
.
cleaned_data
[
name
]
=
value
#
self.cleaned_data[name] = value
except
ValidationError
as
e
:
#
except ValidationError as e:
self
.
_errors
[
name
]
=
self
.
error_class
(
e
.
messages
)
#
self._errors[name] = self.error_class(e.messages)
if
name
in
self
.
cleaned_data
:
#
if name in self.cleaned_data:
del
self
.
cleaned_data
[
name
]
#
del self.cleaned_data[name]
elif
old
:
#
elif old:
if
name
==
'networks'
:
#
if name == 'networks':
self
.
cleaned_data
[
name
]
=
[
#
self.cleaned_data[name] = [
i
.
vlan
for
i
in
self
.
instance
.
interface_set
.
all
()]
#
i.vlan for i in self.instance.interface_set.all()]
else
:
#
else:
self
.
cleaned_data
[
name
]
=
getattr
(
old
,
name
)
#
self.cleaned_data[name] = getattr(old, name)
if
"req_traits"
not
in
self
.
allowed_fields
:
#
if "req_traits" not in self.allowed_fields:
self
.
cleaned_data
[
'req_traits'
]
=
self
.
instance
.
req_traits
.
all
()
#
self.cleaned_data['req_traits'] = self.instance.req_traits.all()
def
save
(
self
,
commit
=
True
):
def
save
(
self
,
commit
=
True
):
data
=
self
.
cleaned_data
data
=
self
.
cleaned_data
...
@@ -1300,12 +1306,7 @@ class UserEditForm(forms.ModelForm):
...
@@ -1300,12 +1306,7 @@ class UserEditForm(forms.ModelForm):
class
AclUserOrGroupAddForm
(
forms
.
Form
):
class
AclUserOrGroupAddForm
(
forms
.
Form
):
name
=
forms
.
CharField
(
name
=
forms
.
CharField
(
required
=
False
)
widget
=
autocomplete
.
ListSelect2
(
url
=
'autocomplete.acl.user-group'
,
attrs
=
{
'class'
:
'form-control'
,
'data-html'
:
'true'
,
'data-placeholder'
:
_
(
"Name of group or user"
)}))
class
TransferOwnershipForm
(
forms
.
Form
):
class
TransferOwnershipForm
(
forms
.
Form
):
...
...
circle/dashboard/templates/dashboard/_manage_access.html
View file @
4c29a7bb
{% load i18n %}
{% load i18n %}
<form
action=
"{{
acl.
url }}"
method=
"post"
>
{% csrf_token %}
<form
action=
"{{
manage_access_
url }}"
method=
"post"
>
{% csrf_token %}
<table
class=
"table table-striped table-with-form-fields acl-table"
id=
"{{table_id}}"
>
<table
class=
"table table-striped table-with-form-fields acl-table"
id=
"{{table_id}}"
>
<thead>
<thead>
<tr>
<tr>
<th></th>
<th></th>
<th>
{% trans "Who" %}
</th>
<th>
{% trans "Who" %}
</th>
<th>
{% trans "What" %}
</th>
<th><i
id=
"manage-access-select-all"
class=
"fa fa-times"
></i></th>
<th><i
id=
"manage-access-select-all"
class=
"fa fa-times"
></i></th>
</tr>
</tr>
</thead>
</thead>
<tbody>
<tbody>
{% for
i in acl.
users %}
{% for
member in shared_with_
users %}
<tr>
<tr>
<td>
<td>
<img
class=
"profile-avatar"
src=
"{{ i.user.profile.get_avatar_url }}"
/>
{#
<img
class=
"profile-avatar"
src=
"{{ i.user.profile.get_avatar_url }}"
/>
#}
</td>
</td>
<td>
<td>
<a
href=
"{% url "
dashboard
.
views
.
profile
"
username=
i.user.username
%}"
{#
<a
href=
"{% url "
dashboard
.
views
.
profile
"
username=
i.user.username
%}"#}
title=
"{{ i.user.username }}"
>
{#
title=
"{{ i.user.username }}"
>
#}
{% include "dashboard/_display-name.html" with user=i.user show_org=True %}
{# {% include "dashboard/_display-name.html" with user=i.user show_org=True %}#}
</a>
{#
</a>
#}
</td>
{{ member.username }}
<td>
<select
class=
"form-control"
name=
"perm-u-{{i.user.id}}"
{%
if
i
.
level
not
in
acl
.
allowed_levels
%}
disabled
{%
endif
%}
>
{% for id, name in acl.levels %}
<option
{%
if
id =
=
i
.
level
%}
selected=
"selected"
{%
endif
%}
{%
if
id
not
in
acl
.
allowed_levels
%}
disabled
{%
endif
%}
value=
"{{id}}"
>
{{name}}
</option>
{% endfor %}
</select>
</td>
</td>
<td>
<td>
<input
type=
"checkbox"
name=
"remove-u-{{
i.user.id
}}"
title=
"{% trans "
Remove
"
%}"
/>
<input
type=
"checkbox"
name=
"remove-u-{{
member.member_id
}}"
title=
"{% trans "
Remove
"
%}"
/>
</td>
</td>
</tr>
</tr>
{% endfor %}
{% endfor %}
...
@@ -57,15 +48,10 @@
...
@@ -57,15 +48,10 @@
</td>
</td>
</tr>
</tr>
{% endfor %}
{% endfor %}
<tr><td><i
class=
"fa fa-plus"
></i></td>
<tr>
<td><i
class=
"fa fa-plus"
></i></td>
<td>
{{aclform.name }}
</td>
<td>
{{aclform.name }}
</td>
<td><select
class=
"form-control"
name=
"level"
>
<td></td>
{% for id, name in acl.levels %}
{% if id in acl.allowed_levels %}
<option
value=
"{{id}}"
>
{{name}}
</option>
{% endif %}
{% endfor %}
</select></td><td></td>
</tr>
</tr>
</tbody>
</tbody>
</table>
</table>
...
...
circle/dashboard/templates/dashboard/template-edit.html
View file @
4c29a7bb
...
@@ -42,8 +42,8 @@
...
@@ -42,8 +42,8 @@
{# {{ form.max_ram_size|as_crispy_field }}#}
{# {{ form.max_ram_size|as_crispy_field }}#}
</fieldset>
</fieldset>
<fieldset>
{#
<fieldset>
#}
<legend>
{% trans "Virtual machine settings" %}
</legend>
{#
<legend>
{% trans "Virtual machine settings" %}
</legend>
#}
{# {{ form.arch|as_crispy_field }}#}
{# {{ form.arch|as_crispy_field }}#}
{# {{ form.access_method|as_crispy_field }}#}
{# {{ form.access_method|as_crispy_field }}#}
{# {{ form.boot_menu|as_crispy_field }}#}
{# {{ form.boot_menu|as_crispy_field }}#}
...
@@ -52,11 +52,11 @@
...
@@ -52,11 +52,11 @@
{# {{ form.description|as_crispy_field }}#}
{# {{ form.description|as_crispy_field }}#}
{# {{ form.system|as_crispy_field }}#}
{# {{ form.system|as_crispy_field }}#}
{# {{ form.has_agent|as_crispy_field }}#}
{# {{ form.has_agent|as_crispy_field }}#}
</fieldset>
{#
</fieldset>
#}
<fieldset>
<fieldset>
<legend>
{% trans "External resources" %}
</legend>
<legend>
{% trans "External resources" %}
</legend>
{# {{ form.networks|as_crispy_field }}#}
{# {{ form.networks|as_crispy_field }}#}
{# {{ form.lease|as_crispy_field }}#
}
{{ form.lease|as_crispy_field }
}
{##}
{##}
{# {{ form.tags|as_crispy_field }}#}
{# {{ form.tags|as_crispy_field }}#}
</fieldset>
</fieldset>
...
@@ -82,26 +82,6 @@
...
@@ -82,26 +82,6 @@
<div
class=
"panel panel-default"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<div
class=
"panel-heading"
>
<h4
class=
"no-margin"
><i
class=
"fa fa-user"
></i>
{% trans "Owner" %}
</h4>
</div>
<div
class=
"panel-body"
>
{% if user == object.owner %}
{% blocktrans %}You are the current owner of this template.{% endblocktrans %}
{% else %}
{% url "dashboard.views.profile" username=object.owner.username as url %}
{% blocktrans with owner=object.owner name=object.owner.get_full_name%}
The current owner of this template is
<a
href=
"{{url}}"
>
{{name}} ({{owner}})
</a>
.
{% endblocktrans %}
{% endif %}
{% if user == object.owner or user.is_superuser %}
<a
href=
"{% url "
dashboard
.
views
.
template-transfer-ownership
"
object
.
pk
%}"
class=
"btn btn-link tx-tpl-ownership"
>
{% trans "Transfer ownership..." %}
</a>
{% endif %}
</div>
</div>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h4
class=
"no-margin"
><i
class=
"fa fa-group"
></i>
{% trans "Manage access" %}
</h4>
<h4
class=
"no-margin"
><i
class=
"fa fa-group"
></i>
{% trans "Manage access" %}
</h4>
</div>
</div>
<div
class=
"panel-body"
>
<div
class=
"panel-body"
>
...
@@ -111,40 +91,6 @@
...
@@ -111,40 +91,6 @@
<div
class=
"panel panel-default"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<div
class=
"panel-heading"
>
<h4
class=
"no-margin"
>
<i
class=
"fa fa-question-circle"
></i>
{% trans "Access level rights" %}
</h4>
</div>
<div
class=
"panel-body"
>
<dl>
<dt>
{% trans "User" %}
</dt>
<dd>
{% blocktrans %}
User can deploy instances from this template.
{% endblocktrans %}
</dd>
<dt>
{% trans "Operator" %}
</dt>
<dd>
{% blocktrans %}
Operators are able to deploy and grant/revoke User level access to this template.
{% endblocktrans %}
</dd>
<dt>
{% trans "Owner" %}
</dt>
<dd>
{% blocktrans %}
Owners can edit attributes or delete the template.
Owners are able to grant/revoke User, Operator and Owner level access to the template.
The accountable owner (the one who created the template) can not be demoted.
The accountable ownership can be transferred to other User via the "Transfer onwership" button.
{% endblocktrans %}
</dd>
</dl>
</div>
</div>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h4
class=
"no-margin"
><i
class=
"fa fa-file"
></i>
{% trans "Disk list" %}
</h4>
<h4
class=
"no-margin"
><i
class=
"fa fa-file"
></i>
{% trans "Disk list" %}
</h4>
</div>
</div>
<div
class=
"panel-body"
>
<div
class=
"panel-body"
>
...
...
circle/dashboard/urls.py
View file @
4c29a7bb
...
@@ -19,7 +19,8 @@ from __future__ import absolute_import
...
@@ -19,7 +19,8 @@ from __future__ import absolute_import
from
dashboard.views.autocomplete
import
AclUserGroupAutocomplete
,
AclUserAutocomplete
from
dashboard.views.autocomplete
import
AclUserGroupAutocomplete
,
AclUserAutocomplete
from
dashboard.views.template
import
TemplateList
,
TemplateChoose
,
TemplateDetail
,
TemplateDelete
,
\
from
dashboard.views.template
import
TemplateList
,
TemplateChoose
,
TemplateDetail
,
TemplateDelete
,
\
TransferTemplateOwnershipConfirmView
,
TransferTemplateOwnershipView
,
LeaseCreate
,
LeaseDetail
,
LeaseDelete
TransferTemplateOwnershipConfirmView
,
TransferTemplateOwnershipView
,
LeaseCreate
,
LeaseDetail
,
LeaseDelete
,
\
TemplateAclUpdateView
from
dashboard.views.vm
import
VmDetailView
,
VmList
,
VmCreate
,
vm_activity
,
vm_ops
,
FavouriteView
,
VmPlainImageCreate
from
dashboard.views.vm
import
VmDetailView
,
VmList
,
VmCreate
,
vm_activity
,
vm_ops
,
FavouriteView
,
VmPlainImageCreate
from
django.conf.urls
import
url
from
django.conf.urls
import
url
...
@@ -47,8 +48,8 @@ urlpatterns = [
...
@@ -47,8 +48,8 @@ urlpatterns = [
# name="dashboard.views.template-create"),
# name="dashboard.views.template-create"),
url
(
r'^template/choose/$'
,
TemplateChoose
.
as_view
(),
url
(
r'^template/choose/$'
,
TemplateChoose
.
as_view
(),
name
=
"dashboard.views.template-choose"
),
name
=
"dashboard.views.template-choose"
),
#
url(r'template/(?P<pk>\d+)/acl/$', TemplateAclUpdateView.as_view(),
url
(
r'template/(?P<pk>\d+)/acl/$'
,
TemplateAclUpdateView
.
as_view
(),
#
name='dashboard.views.template-acl'),
name
=
'dashboard.views.template-acl'
),
url
(
r'^template/(?P<pk>\d+)/$'
,
TemplateDetail
.
as_view
(),
url
(
r'^template/(?P<pk>\d+)/$'
,
TemplateDetail
.
as_view
(),
name
=
'dashboard.views.template-detail'
),
name
=
'dashboard.views.template-detail'
),
url
(
r"^template/list/$"
,
TemplateList
.
as_view
(),
url
(
r"^template/list/$"
,
TemplateList
.
as_view
(),
...
...
circle/dashboard/views/autocomplete.py
View file @
4c29a7bb
...
@@ -89,10 +89,9 @@ class AclUserAutocomplete(autocomplete.Select2ListView):
...
@@ -89,10 +89,9 @@ class AclUserAutocomplete(autocomplete.Select2ListView):
}),
content_type
=
"application/json"
)
}),
content_type
=
"application/json"
)
class
AclUserGroupAutocomplete
(
AclUserAutocomplete
):
class
AclUserGroupAutocomplete
(
autocomplete
.
Select2ListView
):
# TODO: was AclUserAutocomplete inherited
group_search_fields
=
(
'name'
,
'groupprofile__org_id'
)
def
get_list
(
self
):
def
get_list
(
self
):
groups
=
AclUpdateView
.
get_allowed_groups
(
self
.
request
.
user
)
from
openstack_api
import
keystone
groups
=
self
.
filter
(
groups
,
self
.
group_search_fields
)
groups
=
keystone
.
group_list
(
request
=
self
.
request
,
user
=
self
.
request
.
user
)
return
super
(
AclUserGroupAutocomplete
,
self
)
.
get_list
()
+
groups
group_names
=
[
g
.
name
for
g
in
groups
]
return
super
(
AclUserGroupAutocomplete
,
self
)
.
get_list
()
+
group_names
circle/dashboard/views/template.py
View file @
4c29a7bb
...
@@ -41,6 +41,9 @@ from braces.views import (
...
@@ -41,6 +41,9 @@ from braces.views import (
)
)
from
django.views.generic.edit
import
BaseUpdateView
,
ModelFormMixin
,
FormView
from
django.views.generic.edit
import
BaseUpdateView
,
ModelFormMixin
,
FormView
from
django_tables2
import
SingleTableView
from
django_tables2
import
SingleTableView
from
keystoneauth1
import
session
from
keystoneauth1.identity
import
v3
from
openstack_auth.utils
import
fix_auth_url_version
from
vm.models
import
(
from
vm.models
import
(
InstanceTemplate
,
InterfaceTemplate
,
Instance
,
Lease
,
InstanceActivity
InstanceTemplate
,
InterfaceTemplate
,
Instance
,
Lease
,
InstanceActivity
...
@@ -289,6 +292,8 @@ class TemplateDelete(DeleteViewBase):
...
@@ -289,6 +292,8 @@ class TemplateDelete(DeleteViewBase):
object
.
delete
()
object
.
delete
()
class
TemplateDetail
(
LoginRequiredMixin
,
GraphMixin
,
SuccessMessageMixin
,
UpdateView
):
class
TemplateDetail
(
LoginRequiredMixin
,
GraphMixin
,
SuccessMessageMixin
,
UpdateView
):
model
=
InstanceTemplate
model
=
InstanceTemplate
template_name
=
"dashboard/template-edit.html"
template_name
=
"dashboard/template-edit.html"
...
@@ -325,11 +330,57 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
...
@@ -325,11 +330,57 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
else
:
else
:
return
super
(
TemplateDetail
,
self
)
.
get
(
request
,
*
args
,
**
kwargs
)
return
super
(
TemplateDetail
,
self
)
.
get
(
request
,
*
args
,
**
kwargs
)
def
__get_glance_admin_client
(
self
,
request
):
from
keystoneauth1
import
session
from
glanceclient
import
Client
auth
=
v3
.
Password
(
auth_url
=
fix_auth_url_version
(
settings
.
OPENSTACK_KEYSTONE_URL
),
user_id
=
settings
.
OPENSTACK_CIRCLE_USERID
,
password
=
settings
.
OPENSTACK_CIRCLE_PASSWORD
,
project_id
=
request
.
user
.
tenant_id
,
)
session
=
session
.
Session
(
auth
=
auth
,
verify
=
False
)
return
Client
(
'2'
,
session
=
session
)
def
__get_members_shared_with
(
self
):
template
=
self
.
get_object
()
glance
=
self
.
__get_glance_admin_client
(
self
.
request
)
members_generator
=
glance
.
image_members
.
list
(
template
.
image_id
)
return
[
m
for
m
in
members_generator
]
def
__get_project_client
(
self
,
project_id
):
from
keystoneclient.v3
import
client
auth
=
v3
.
Password
(
auth_url
=
fix_auth_url_version
(
settings
.
OPENSTACK_KEYSTONE_URL
),
user_id
=
settings
.
OPENSTACK_CIRCLE_USERID
,
password
=
settings
.
OPENSTACK_CIRCLE_PASSWORD
,
project_id
=
project_id
,
)
sess
=
session
.
Session
(
auth
=
auth
,
verify
=
False
)
return
client
.
Client
(
session
=
sess
,
interface
=
'public'
)
def
__get_username_for
(
self
,
project_id
):
client
=
self
.
__get_project_client
(
project_id
)
project
=
client
.
projects
.
get
(
project_id
)
return
project
.
name
# as username = project's name
def
__get_users_shared_with
(
self
):
members
=
self
.
__get_members_shared_with
()
for
m
in
members
:
m
[
'username'
]
=
self
.
__get_username_for
(
m
.
member_id
)
return
members
def
get_context_data
(
self
,
**
kwargs
):
def
get_context_data
(
self
,
**
kwargs
):
template
=
self
.
get_object
()
template
=
self
.
get_object
()
context
=
super
(
TemplateDetail
,
self
)
.
get_context_data
(
**
kwargs
)
context
=
super
(
TemplateDetail
,
self
)
.
get_context_data
(
**
kwargs
)
# context['acl'] = AclUpdateView.get_acl_data(
# context['acl'] = AclUpdateView.get_acl_data(
# template, self.request.user, 'dashboard.views.template-acl')
# template, self.request.user, 'dashboard.views.template-acl')
context
[
'manage_access_url'
]
=
reverse_lazy
(
'dashboard.views.template-acl'
,
kwargs
=
{
'pk'
:
template
.
id
})
context
[
'shared_with_users'
]
=
self
.
__get_users_shared_with
()
# context['disks'] = template.disks.all()
# context['disks'] = template.disks.all()
context
[
'is_owner'
]
=
template
.
owner_id
==
self
.
request
.
user
.
id
context
[
'is_owner'
]
=
template
.
owner_id
==
self
.
request
.
user
.
id
context
[
'aclform'
]
=
AclUserOrGroupAddForm
()
context
[
'aclform'
]
=
AclUserOrGroupAddForm
()
...
@@ -341,13 +392,14 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
...
@@ -341,13 +392,14 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
return
reverse_lazy
(
"dashboard.views.template-detail"
,
return
reverse_lazy
(
"dashboard.views.template-detail"
,
kwargs
=
self
.
kwargs
)
kwargs
=
self
.
kwargs
)
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
def
post
(
self
,
request
):
template
=
self
.
get_object
()
template
=
self
.
get_object
()
if
not
template
.
has_level
(
request
.
user
,
'owner'
):
# TODO: multiple users
if
template
.
owner_id
!=
request
.
user
.
id
:
raise
PermissionDenied
()
raise
PermissionDenied
()
return
super
(
TemplateDetail
,
self
)
.
post
(
self
,
request
,
args
,
kwargs
)
return
super
(
TemplateDetail
,
self
)
.
post
(
self
,
request
,
args
,
kwargs
)
def
get_form_kwargs
(
self
):
def
get_form_kwargs
(
self
,
*
args
,
**
kwargs
):
kwargs
=
super
(
TemplateDetail
,
self
)
.
get_form_kwargs
()
kwargs
=
super
(
TemplateDetail
,
self
)
.
get_form_kwargs
()
kwargs
[
'user'
]
=
self
.
request
.
user
kwargs
[
'user'
]
=
self
.
request
.
user
return
kwargs
return
kwargs
...
...
circle/dashboard/views/util.py
View file @
4c29a7bb
...
@@ -43,6 +43,8 @@ from django.views.decorators.csrf import csrf_protect
...
@@ -43,6 +43,8 @@ from django.views.decorators.csrf import csrf_protect
from
django.views.decorators.debug
import
sensitive_post_parameters
from
django.views.decorators.debug
import
sensitive_post_parameters
from
django.views.generic
import
DetailView
,
View
,
DeleteView
,
FormView
from
django.views.generic
import
DetailView
,
View
,
DeleteView
,
FormView
from
django.views.generic.detail
import
SingleObjectMixin
from
django.views.generic.detail
import
SingleObjectMixin
from
keystoneauth1.identity
import
v3
from
openstack_auth.utils
import
fix_auth_url_version
from
vm.models
import
Instance
from
vm.models
import
Instance
from
..models
import
GroupProfile
from
..models
import
GroupProfile
...
@@ -411,159 +413,141 @@ class RequestFormOperationMixin(FormOperationMixin):
...
@@ -411,159 +413,141 @@ class RequestFormOperationMixin(FormOperationMixin):
class
AclUpdateView
(
LoginRequiredMixin
,
View
,
SingleObjectMixin
):
class
AclUpdateView
(
LoginRequiredMixin
,
View
,
SingleObjectMixin
):
def
send_success_message
(
self
,
whom
,
old_level
,
new_level
):
# def send_success_message(self, whom, old_level, new_level):
if
old_level
and
new_level
:
# if old_level and new_level:
msg
=
_
(
"Acl user/group
%(w)
s successfully modified."
)
# msg = _("Acl user/group %(w)s successfully modified.")
elif
not
old_level
and
new_level
:
# elif not old_level and new_level:
msg
=
_
(
"Acl user/group
%(w)
s successfully added."
)
# msg = _("Acl user/group %(w)s successfully added.")
elif
old_level
and
not
new_level
:
# elif old_level and not new_level:
msg
=
_
(
"Acl user/group
%(w)
s successfully removed."
)
# msg = _("Acl user/group %(w)s successfully removed.")
if
msg
:
# if msg:
messages
.
success
(
self
.
request
,
msg
%
{
'w'
:
whom
})
# messages.success(self.request, msg % {'w': whom})
#
def
get_level
(
self
,
whom
):
# def add_levels(self):
for
u
,
level
in
self
.
acl_data
:
# self.request, _('User "%s" has already '
if
u
==
whom
:
# 'access to this object.') % name)
return
level
# self.request, _('Group "%s" has already '
# 'access to this object.') % name)
# self.request, _('User or group "%s" not found.') % name)
def
__get_current_users_groups
(
self
,
request
):
from
openstack_api
import
keystone
groups
=
keystone
.
group_list
(
request
=
self
.
request
,
user
=
self
.
request
.
user
)
return
[
g
.
name
for
g
in
groups
]
def
__get_glance_admin_client
(
self
,
project_id
):
from
keystoneauth1
import
session
from
glanceclient
import
Client
auth
=
v3
.
Password
(
auth_url
=
fix_auth_url_version
(
settings
.
OPENSTACK_KEYSTONE_URL
),
user_id
=
settings
.
OPENSTACK_CIRCLE_USERID
,
password
=
settings
.
OPENSTACK_CIRCLE_PASSWORD
,
project_id
=
project_id
,
)
session
=
session
.
Session
(
auth
=
auth
,
verify
=
False
)
return
Client
(
'2'
,
session
=
session
)
def
__get_all_projects
(
self
):
from
keystoneauth1
import
session
from
keystoneclient.v3
import
client
auth
=
v3
.
Password
(
auth_url
=
fix_auth_url_version
(
settings
.
OPENSTACK_KEYSTONE_URL
),
user_id
=
settings
.
OPENSTACK_CIRCLE_USERID
,
password
=
settings
.
OPENSTACK_CIRCLE_PASSWORD
,
)
sess
=
session
.
Session
(
auth
=
auth
,
verify
=
False
)
keystone
=
client
.
Client
(
session
=
sess
,
interface
=
settings
.
OPENSTACK_INTERFACE
)
return
keystone
.
projects
.
list
(
domain
=
settings
.
OPENSTACK_CIRCLE_DOMAIN_ID
,
user
=
settings
.
OPENSTACK_CIRCLE_USERID
)
def
__get_project_id_by_name
(
self
,
request
,
name
):
projects
=
self
.
__get_all_projects
()
for
p
in
projects
:
if
p
.
name
==
name
:
return
p
.
id
return
None
return
None
@classmethod
def
__accept_membership
(
self
,
membership
,
project_id_of_user
):
def
get_acl_data
(
cls
,
obj
,
user
,
url
):
template
=
self
.
get_object
()
levels
=
obj
.
ACL_LEVELS
glance
=
self
.
__get_glance_admin_client
(
project_id_of_user
)
allowed_levels
=
list
(
l
for
l
in
OrderedDict
(
levels
)
glance
.
image_members
.
update
(
template
.
image_id
,
project_id_of_user
,
'accepted'
)
if
cls
.
has_next_level
(
user
,
obj
,
l
))
is_owner
=
'owner'
in
allowed_levels
def
__handle_group_assignment
(
self
,
request
):
template
=
self
.
get_object
()
allowed_users
=
cls
.
get_allowed_users
(
user
)
pass
allowed_groups
=
(
set
(
cls
.
get_allowed_groups
(
user
))
|
set
(
user
.
groups
.
all
()))
def
__handle_user_assignment
(
self
,
request
):
glance
=
self
.
__get_glance_admin_client
(
request
.
user
.
tenant_id
)
user_levels
=
list
(
template
=
self
.
get_object
()
{
'user'
:
u
,
'level'
:
l
}
for
u
,
l
in
obj
.
get_users_with_level
()
new_template_user
=
request
.
POST
[
'name'
]
if
is_owner
or
u
==
user
or
u
in
allowed_users
)
project_id_of_user
=
self
.
__get_project_id_by_name
(
request
,
new_template_user
group_levels
=
list
(
)
{
'group'
:
g
,
'level'
:
l
}
for
g
,
l
in
obj
.
get_groups_with_level
()
if
is_owner
or
g
in
allowed_groups
)
return
{
'users'
:
user_levels
,
'groups'
:
group_levels
,
'levels'
:
levels
,
'allowed_levels'
:
allowed_levels
,
'url'
:
reverse
(
url
,
args
=
[
obj
.
pk
])}
@classmethod
old_members_generator
=
glance
.
image_members
.
list
(
template
.
image_id
)
def
has_next_level
(
self
,
user
,
instance
,
level
):
old_members
=
[
m
.
member_id
for
m
in
old_members_generator
]
levels
=
OrderedDict
(
instance
.
ACL_LEVELS
)
.
keys
()
next_levels
=
dict
(
zip
([
None
]
+
levels
,
levels
+
levels
[
-
1
:]))
# {None: 'user', 'user': 'operator', 'operator: 'owner',
# 'owner: 'owner'}
next_level
=
next_levels
[
level
]
return
instance
.
has_level
(
user
,
next_level
)
@classmethod
if
project_id_of_user
in
old_members
:
def
get_allowed_groups
(
cls
,
user
):
msg
=
_
(
"Template is already shared with
%
s"
%
new_template_user
)
if
user
.
has_perm
(
'dashboard.use_autocomplete'
):
messages
.
warning
(
self
.
request
,
msg
)
return
Group
.
objects
.
all
()
elif
project_id_of_user
is
not
None
:
membership
=
glance
.
image_members
.
create
(
template
.
image_id
,
project_id_of_user
)
self
.
__accept_membership
(
membership
,
project_id_of_user
)
msg
=
_
(
"Successfully shared with
%
s"
%
new_template_user
)
messages
.
success
(
self
.
request
,
msg
)
else
:
else
:
profiles
=
GroupProfile
.
get_objects_with_level
(
'owner'
,
user
)
msg
=
_
(
"User or group with name '
%
s' doesn't exist"
%
new_template_
user
)
return
Group
.
objects
.
filter
(
groupprofile__in
=
profiles
)
.
distinct
(
)
messages
.
error
(
self
.
request
,
msg
)
@classmethod
def
__handle_assignments
(
self
,
request
):
def
get_allowed_users
(
cls
,
user
):
current_users_groups
=
self
.
__get_current_users_groups
(
request
)
if
user
.
has_perm
(
'dashboard.use_autocomplete'
):
new_template_user
=
request
.
POST
[
'name'
]
return
User
.
objects
.
all
()
else
:
if
new_template_user
is
not
None
and
len
(
new_template_user
)
>
0
:
groups
=
cls
.
get_allowed_groups
(
user
)
if
new_template_user
in
current_users_groups
:
return
User
.
objects
.
filter
(
self
.
__handle_group_assignment
(
request
)
Q
(
groups__in
=
groups
)
|
Q
(
pk
=
user
.
pk
))
.
distinct
()
def
check_auth
(
self
,
whom
,
old_level
,
new_level
):
if
isinstance
(
whom
,
Group
):
if
(
not
self
.
is_owner
and
whom
not
in
AclUpdateView
.
get_allowed_groups
(
self
.
request
.
user
)):
return
False
elif
isinstance
(
whom
,
User
):
if
(
not
self
.
is_owner
and
whom
not
in
AclUpdateView
.
get_allowed_users
(
self
.
request
.
user
)):
return
False
return
(
AclUpdateView
.
has_next_level
(
self
.
request
.
user
,
self
.
instance
,
new_level
)
and
AclUpdateView
.
has_next_level
(
self
.
request
.
user
,
self
.
instance
,
old_level
))
def
set_level
(
self
,
whom
,
new_level
):
user
=
self
.
request
.
user
old_level
=
self
.
get_level
(
whom
)
if
old_level
==
new_level
:
return
if
getattr
(
self
.
instance
,
"owner"
,
None
)
==
whom
:
logger
.
info
(
"Tried to set owner's acl level for
%
s by
%
s."
,
unicode
(
self
.
instance
),
unicode
(
user
))
msg
=
_
(
"The original owner cannot be removed, however "
"you can transfer ownership."
)
if
not
getattr
(
self
,
'hide_messages'
,
False
):
messages
.
warning
(
self
.
request
,
msg
)
elif
self
.
check_auth
(
whom
,
old_level
,
new_level
):
logger
.
info
(
u"Set
%
s's acl level for
%
s to
%
s by
%
s."
,
unicode
(
whom
),
unicode
(
self
.
instance
),
new_level
,
unicode
(
user
))
if
not
getattr
(
self
,
'hide_messages'
,
False
):
self
.
send_success_message
(
whom
,
old_level
,
new_level
)
self
.
instance
.
set_level
(
whom
,
new_level
)
else
:
else
:
logger
.
warning
(
self
.
__handle_user_assignment
(
request
)
u"Tried to set
%
s's acl_level for
%
s (
%
s->
%
s) by
%
s."
,
unicode
(
whom
),
unicode
(
self
.
instance
),
old_level
,
new_level
,
def
__get_removes
(
self
,
request
):
unicode
(
user
))
removes
=
{
"u"
:
[],
def
set_or_remove_levels
(
self
):
"g"
:
[],
for
key
,
value
in
self
.
request
.
POST
.
items
():
}
m
=
re
.
match
(
'(perm|remove)-([ug])-(
\
d+)'
,
key
)
for
key
,
value
in
request
.
POST
.
items
():
m
=
re
.
match
(
'remove-([ug])-(.+)'
,
key
)
if
m
:
if
m
:
cmd
,
typ
,
id
=
m
.
groups
()
t
,
name
=
m
.
groups
()
if
cmd
==
'remove'
:
removes
[
t
]
.
append
(
name
)
value
=
None
entity
=
{
'u'
:
User
,
'g'
:
Group
}[
typ
]
.
objects
.
get
(
id
=
id
)
return
removes
self
.
set_level
(
entity
,
value
)
def
__remove_user
(
self
,
request
,
member_id
):
def
add_levels
(
self
):
template
=
self
.
get_object
()
name
=
self
.
request
.
POST
.
get
(
'name'
,
None
)
glance
=
self
.
__get_glance_admin_client
(
request
.
user
.
tenant_id
)
level
=
self
.
request
.
POST
.
get
(
'level'
,
None
)
glance
.
image_members
.
delete
(
template
.
image_id
,
member_id
)
if
not
name
or
not
level
:
return
def
__handle_removes
(
self
,
request
):
try
:
removes
=
self
.
__get_removes
(
request
)
entity
=
search_user
(
name
)
for
member_id
in
removes
[
'u'
]:
if
self
.
instance
.
object_level_set
.
filter
(
users__in
=
[
entity
]):
self
.
__remove_user
(
request
,
member_id
)
messages
.
warning
(
messages
.
success
(
request
,
_
(
"Successfully removed user"
))
self
.
request
,
_
(
'User "
%
s" has already '
'access to this object.'
)
%
name
)
return
except
User
.
DoesNotExist
:
entity
=
None
try
:
entity
=
Group
.
objects
.
get
(
name
=
name
)
if
self
.
instance
.
object_level_set
.
filter
(
groups__in
=
[
entity
]):
messages
.
warning
(
self
.
request
,
_
(
'Group "
%
s" has already '
'access to this object.'
)
%
name
)
return
except
Group
.
DoesNotExist
:
messages
.
warning
(
self
.
request
,
_
(
'User or group "
%
s" not found.'
)
%
name
)
return
self
.
set_level
(
entity
,
level
)
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
instanc
e
=
self
.
get_object
()
templat
e
=
self
.
get_object
()
self
.
is_owner
=
self
.
instance
.
has_level
(
request
.
user
,
'owner'
)
self
.
acl_data
=
(
self
.
instance
.
get_users_with_level
()
+
openstack_api
.
glance
.
image_update
(
request
,
template
.
image_id
,
visibility
=
"shared"
)
self
.
instance
.
get_groups_with_level
()
)
self
.
__handle_assignments
(
request
)
self
.
set_or_remove_levels
(
)
self
.
__handle_removes
(
request
)
self
.
add_levels
()
return
redirect
(
"
%
s#access"
%
self
.
instanc
e
.
get_absolute_url
())
return
redirect
(
"
%
s#access"
%
templat
e
.
get_absolute_url
())
class
GraphMixin
(
object
):
class
GraphMixin
(
object
):
...
...
circle/openstack_api/keystone.py
View file @
4c29a7bb
...
@@ -593,7 +593,7 @@ def group_delete(request, group_id):
...
@@ -593,7 +593,7 @@ def group_delete(request, group_id):
def
group_list
(
request
,
domain
=
None
,
project
=
None
,
user
=
None
,
filters
=
None
):
def
group_list
(
request
,
domain
=
None
,
project
=
None
,
user
=
None
,
filters
=
None
):
manager
=
keystoneclient
(
request
,
admin
=
True
)
.
groups
manager
=
keystoneclient
(
request
)
.
groups
# TODO: was admin API
groups
=
[]
groups
=
[]
kwargs
=
{
kwargs
=
{
"domain"
:
domain
,
"domain"
:
domain
,
...
@@ -834,7 +834,7 @@ def remove_tenant_user(request, project=None, user=None, domain=None):
...
@@ -834,7 +834,7 @@ def remove_tenant_user(request, project=None, user=None, domain=None):
def
roles_for_group
(
request
,
group
,
domain
=
None
,
project
=
None
):
def
roles_for_group
(
request
,
group
,
domain
=
None
,
project
=
None
):
manager
=
keystoneclient
(
request
,
admin
=
True
)
.
roles
manager
=
keystoneclient
(
request
)
.
roles
return
manager
.
list
(
group
=
group
,
domain
=
domain
,
project
=
project
)
return
manager
.
list
(
group
=
group
,
domain
=
domain
,
project
=
project
)
...
...
circle/vm/models/instance.py
View file @
4c29a7bb
...
@@ -119,6 +119,15 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
...
@@ -119,6 +119,15 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
abstract
=
True
abstract
=
True
class
TemplateMemberGroup
(
Model
):
group_id
=
CharField
(
blank
=
False
,
max_length
=
100
,
unique
=
True
)
class
Meta
:
app_label
=
'vm'
db_table
=
'vm_template_member_group'
class
InstanceTemplate
(
TimeStampedModel
):
class
InstanceTemplate
(
TimeStampedModel
):
"""Virtual machine template.
"""Virtual machine template.
"""
"""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment