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):
# }))
name
=
forms
.
TextInput
()
flavor
=
forms
.
ChoiceField
([
'a'
,
'b'
,
'c'
])
#
flavor = forms.ChoiceField(['a','b','c'])
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
user
=
kwargs
.
pop
(
"user"
,
None
)
super
(
TemplateForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
allowed_fields
=
(
'name'
,
# 'lease',
)
#
# self.fields['networks'].queryset = ()#Vlan.get_objects_with_level('user', self.user)
#
...
...
@@ -487,34 +493,34 @@ class TemplateForm(forms.ModelForm):
def
clean_max_ram_size
(
self
):
return
self
.
cleaned_data
.
get
(
"ram_size"
,
0
)
def
_clean_fields
(
self
):
try
:
old
=
InstanceTemplate
.
objects
.
get
(
pk
=
self
.
instance
.
pk
)
except
InstanceTemplate
.
DoesNotExist
:
old
=
None
for
name
,
field
in
self
.
fields
.
items
():
if
name
in
self
.
allowed_fields
:
value
=
field
.
widget
.
value_from_datadict
(
self
.
data
,
self
.
files
,
self
.
add_prefix
(
name
))
try
:
value
=
field
.
clean
(
value
)
self
.
cleaned_data
[
name
]
=
value
if
hasattr
(
self
,
'clean_
%
s'
%
name
):
value
=
getattr
(
self
,
'clean_
%
s'
%
name
)()
self
.
cleaned_data
[
name
]
=
value
except
ValidationError
as
e
:
self
.
_errors
[
name
]
=
self
.
error_class
(
e
.
messages
)
if
name
in
self
.
cleaned_data
:
del
self
.
cleaned_data
[
name
]
elif
old
:
if
name
==
'networks'
:
self
.
cleaned_data
[
name
]
=
[
i
.
vlan
for
i
in
self
.
instance
.
interface_set
.
all
()]
else
:
self
.
cleaned_data
[
name
]
=
getattr
(
old
,
name
)
#
def _clean_fields(self):
#
try:
#
old = InstanceTemplate.objects.get(pk=self.instance.pk)
#
except InstanceTemplate.DoesNotExist:
#
old = None
#
for name, field in self.fields.items():
#
if name in self.allowed_fields:
#
value = field.widget.value_from_datadict(
#
self.data, self.files, self.add_prefix(name))
#
try:
#
value = field.clean(value)
#
self.cleaned_data[name] = value
#
if hasattr(self, 'clean_%s' % name):
#
value = getattr(self, 'clean_%s' % name)()
#
self.cleaned_data[name] = value
#
except ValidationError as e:
#
self._errors[name] = self.error_class(e.messages)
#
if name in self.cleaned_data:
#
del self.cleaned_data[name]
#
elif old:
#
if name == 'networks':
#
self.cleaned_data[name] = [
#
i.vlan for i in self.instance.interface_set.all()]
#
else:
#
self.cleaned_data[name] = getattr(old, name)
if
"req_traits"
not
in
self
.
allowed_fields
:
self
.
cleaned_data
[
'req_traits'
]
=
self
.
instance
.
req_traits
.
all
()
#
if "req_traits" not in self.allowed_fields:
#
self.cleaned_data['req_traits'] = self.instance.req_traits.all()
def
save
(
self
,
commit
=
True
):
data
=
self
.
cleaned_data
...
...
@@ -1300,12 +1306,7 @@ class UserEditForm(forms.ModelForm):
class
AclUserOrGroupAddForm
(
forms
.
Form
):
name
=
forms
.
CharField
(
widget
=
autocomplete
.
ListSelect2
(
url
=
'autocomplete.acl.user-group'
,
attrs
=
{
'class'
:
'form-control'
,
'data-html'
:
'true'
,
'data-placeholder'
:
_
(
"Name of group or user"
)}))
name
=
forms
.
CharField
(
required
=
False
)
class
TransferOwnershipForm
(
forms
.
Form
):
...
...
circle/dashboard/templates/dashboard/_manage_access.html
View file @
4c29a7bb
{% 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}}"
>
<thead>
<tr>
<th></th>
<th>
{% trans "Who" %}
</th>
<th>
{% trans "What" %}
</th>
<th><i
id=
"manage-access-select-all"
class=
"fa fa-times"
></i></th>
</tr>
</thead>
<tbody>
{% for
i in acl.
users %}
{% for
member in shared_with_
users %}
<tr>
<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>
<a
href=
"{% url "
dashboard
.
views
.
profile
"
username=
i.user.username
%}"
title=
"{{ i.user.username }}"
>
{% include "dashboard/_display-name.html" with user=i.user show_org=True %}
</a>
</td>
<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>
{#
<a
href=
"{% url "
dashboard
.
views
.
profile
"
username=
i.user.username
%}"#}
{#
title=
"{{ i.user.username }}"
>
#}
{# {% include "dashboard/_display-name.html" with user=i.user show_org=True %}#}
{#
</a>
#}
{{ member.username }}
</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>
</tr>
{% endfor %}
...
...
@@ -57,15 +48,10 @@
</td>
</tr>
{% endfor %}
<tr><td><i
class=
"fa fa-plus"
></i></td>
<tr>
<td><i
class=
"fa fa-plus"
></i></td>
<td>
{{aclform.name }}
</td>
<td><select
class=
"form-control"
name=
"level"
>
{% for id, name in acl.levels %}
{% if id in acl.allowed_levels %}
<option
value=
"{{id}}"
>
{{name}}
</option>
{% endif %}
{% endfor %}
</select></td><td></td>
<td></td>
</tr>
</tbody>
</table>
...
...
circle/dashboard/templates/dashboard/template-edit.html
View file @
4c29a7bb
...
...
@@ -42,8 +42,8 @@
{# {{ form.max_ram_size|as_crispy_field }}#}
</fieldset>
<fieldset>
<legend>
{% trans "Virtual machine settings" %}
</legend>
{#
<fieldset>
#}
{#
<legend>
{% trans "Virtual machine settings" %}
</legend>
#}
{# {{ form.arch|as_crispy_field }}#}
{# {{ form.access_method|as_crispy_field }}#}
{# {{ form.boot_menu|as_crispy_field }}#}
...
...
@@ -52,11 +52,11 @@
{# {{ form.description|as_crispy_field }}#}
{# {{ form.system|as_crispy_field }}#}
{# {{ form.has_agent|as_crispy_field }}#}
</fieldset>
{#
</fieldset>
#}
<fieldset>
<legend>
{% trans "External resources" %}
</legend>
{# {{ form.networks|as_crispy_field }}#}
{# {{ form.lease|as_crispy_field }}#
}
{{ form.lease|as_crispy_field }
}
{##}
{# {{ form.tags|as_crispy_field }}#}
</fieldset>
...
...
@@ -82,26 +82,6 @@
<div
class=
"panel panel-default"
>
<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>
</div>
<div
class=
"panel-body"
>
...
...
@@ -111,40 +91,6 @@
<div
class=
"panel panel-default"
>
<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>
</div>
<div
class=
"panel-body"
>
...
...
circle/dashboard/urls.py
View file @
4c29a7bb
...
...
@@ -19,7 +19,8 @@ from __future__ import absolute_import
from
dashboard.views.autocomplete
import
AclUserGroupAutocomplete
,
AclUserAutocomplete
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
django.conf.urls
import
url
...
...
@@ -47,8 +48,8 @@ urlpatterns = [
# name="dashboard.views.template-create"),
url
(
r'^template/choose/$'
,
TemplateChoose
.
as_view
(),
name
=
"dashboard.views.template-choose"
),
#
url(r'template/(?P<pk>\d+)/acl/$', TemplateAclUpdateView.as_view(),
#
name='dashboard.views.template-acl'),
url
(
r'template/(?P<pk>\d+)/acl/$'
,
TemplateAclUpdateView
.
as_view
(),
name
=
'dashboard.views.template-acl'
),
url
(
r'^template/(?P<pk>\d+)/$'
,
TemplateDetail
.
as_view
(),
name
=
'dashboard.views.template-detail'
),
url
(
r"^template/list/$"
,
TemplateList
.
as_view
(),
...
...
circle/dashboard/views/autocomplete.py
View file @
4c29a7bb
...
...
@@ -89,10 +89,9 @@ class AclUserAutocomplete(autocomplete.Select2ListView):
}),
content_type
=
"application/json"
)
class
AclUserGroupAutocomplete
(
AclUserAutocomplete
):
group_search_fields
=
(
'name'
,
'groupprofile__org_id'
)
class
AclUserGroupAutocomplete
(
autocomplete
.
Select2ListView
):
# TODO: was AclUserAutocomplete inherited
def
get_list
(
self
):
groups
=
AclUpdateView
.
get_allowed_groups
(
self
.
request
.
user
)
groups
=
self
.
filter
(
groups
,
self
.
group_search_fields
)
return
super
(
AclUserGroupAutocomplete
,
self
)
.
get_list
()
+
groups
from
openstack_api
import
keystone
groups
=
keystone
.
group_list
(
request
=
self
.
request
,
user
=
self
.
request
.
user
)
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 (
)
from
django.views.generic.edit
import
BaseUpdateView
,
ModelFormMixin
,
FormView
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
(
InstanceTemplate
,
InterfaceTemplate
,
Instance
,
Lease
,
InstanceActivity
...
...
@@ -289,6 +292,8 @@ class TemplateDelete(DeleteViewBase):
object
.
delete
()
class
TemplateDetail
(
LoginRequiredMixin
,
GraphMixin
,
SuccessMessageMixin
,
UpdateView
):
model
=
InstanceTemplate
template_name
=
"dashboard/template-edit.html"
...
...
@@ -325,11 +330,57 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
else
:
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
):
template
=
self
.
get_object
()
context
=
super
(
TemplateDetail
,
self
)
.
get_context_data
(
**
kwargs
)
# context['acl'] = AclUpdateView.get_acl_data(
# 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
[
'is_owner'
]
=
template
.
owner_id
==
self
.
request
.
user
.
id
context
[
'aclform'
]
=
AclUserOrGroupAddForm
()
...
...
@@ -341,13 +392,14 @@ class TemplateDetail(LoginRequiredMixin, GraphMixin, SuccessMessageMixin, Update
return
reverse_lazy
(
"dashboard.views.template-detail"
,
kwargs
=
self
.
kwargs
)
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
def
post
(
self
,
request
):
template
=
self
.
get_object
()
if
not
template
.
has_level
(
request
.
user
,
'owner'
):
# TODO: multiple users
if
template
.
owner_id
!=
request
.
user
.
id
:
raise
PermissionDenied
()
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
[
'user'
]
=
self
.
request
.
user
return
kwargs
...
...
circle/dashboard/views/util.py
View file @
4c29a7bb
...
...
@@ -43,6 +43,8 @@ from django.views.decorators.csrf import csrf_protect
from
django.views.decorators.debug
import
sensitive_post_parameters
from
django.views.generic
import
DetailView
,
View
,
DeleteView
,
FormView
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
..models
import
GroupProfile
...
...
@@ -411,159 +413,141 @@ class RequestFormOperationMixin(FormOperationMixin):
class
AclUpdateView
(
LoginRequiredMixin
,
View
,
SingleObjectMixin
):
def
send_success_message
(
self
,
whom
,
old_level
,
new_level
):
if
old_level
and
new_level
:
msg
=
_
(
"Acl user/group
%(w)
s successfully modified."
)
elif
not
old_level
and
new_level
:
msg
=
_
(
"Acl user/group
%(w)
s successfully added."
)
elif
old_level
and
not
new_level
:
msg
=
_
(
"Acl user/group
%(w)
s successfully removed."
)
if
msg
:
messages
.
success
(
self
.
request
,
msg
%
{
'w'
:
whom
})
def
get_level
(
self
,
whom
):
for
u
,
level
in
self
.
acl_data
:
if
u
==
whom
:
return
level
# def send_success_message(self, whom, old_level, new_level):
# if old_level and new_level:
# msg = _("Acl user/group %(w)s successfully modified.")
# elif not old_level and new_level:
# msg = _("Acl user/group %(w)s successfully added.")
# elif old_level and not new_level:
# msg = _("Acl user/group %(w)s successfully removed.")
# if msg:
# messages.success(self.request, msg % {'w': whom})
#
# def add_levels(self):
# self.request, _('User "%s" has already '
# 'access to this object.') % name)
# 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
@classmethod
def
get_acl_data
(
cls
,
obj
,
user
,
url
):
levels
=
obj
.
ACL_LEVELS
allowed_levels
=
list
(
l
for
l
in
OrderedDict
(
levels
)
if
cls
.
has_next_level
(
user
,
obj
,
l
))
is_owner
=
'owner'
in
allowed_levels
allowed_users
=
cls
.
get_allowed_users
(
user
)
allowed_groups
=
(
set
(
cls
.
get_allowed_groups
(
user
))
|
set
(
user
.
groups
.
all
()))
user_levels
=
list
(
{
'user'
:
u
,
'level'
:
l
}
for
u
,
l
in
obj
.
get_users_with_level
()
if
is_owner
or
u
==
user
or
u
in
allowed_users
)
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
])}
def
__accept_membership
(
self
,
membership
,
project_id_of_user
):
template
=
self
.
get_object
()
glance
=
self
.
__get_glance_admin_client
(
project_id_of_user
)
glance
.
image_members
.
update
(
template
.
image_id
,
project_id_of_user
,
'accepted'
)
def
__handle_group_assignment
(
self
,
request
):
template
=
self
.
get_object
()
pass
def
__handle_user_assignment
(
self
,
request
):
glance
=
self
.
__get_glance_admin_client
(
request
.
user
.
tenant_id
)
template
=
self
.
get_object
()
new_template_user
=
request
.
POST
[
'name'
]
project_id_of_user
=
self
.
__get_project_id_by_name
(
request
,
new_template_user
)
@classmethod
def
has_next_level
(
self
,
user
,
instance
,
level
):
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
)
old_members_generator
=
glance
.
image_members
.
list
(
template
.
image_id
)
old_members
=
[
m
.
member_id
for
m
in
old_members_generator
]
@classmethod
def
get_allowed_groups
(
cls
,
user
):
if
user
.
has_perm
(
'dashboard.use_autocomplete'
):
return
Group
.
objects
.
all
()
if
project_id_of_user
in
old_members
:
msg
=
_
(
"Template is already shared with
%
s"
%
new_template_user
)
messages
.
warning
(
self
.
request
,
msg
)
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
:
profiles
=
GroupProfile
.
get_objects_with_level
(
'owner'
,
user
)
return
Group
.
objects
.
filter
(
groupprofile__in
=
profiles
)
.
distinct
(
)
msg
=
_
(
"User or group with name '
%
s' doesn't exist"
%
new_template_
user
)
messages
.
error
(
self
.
request
,
msg
)
@classmethod
def
get_allowed_users
(
cls
,
user
):
if
user
.
has_perm
(
'dashboard.use_autocomplete'
):
return
User
.
objects
.
all
()
else
:
groups
=
cls
.
get_allowed_groups
(
user
)
return
User
.
objects
.
filter
(
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
)
def
__handle_assignments
(
self
,
request
):
current_users_groups
=
self
.
__get_current_users_groups
(
request
)
new_template_user
=
request
.
POST
[
'name'
]
if
new_template_user
is
not
None
and
len
(
new_template_user
)
>
0
:
if
new_template_user
in
current_users_groups
:
self
.
__handle_group_assignment
(
request
)
else
:
logger
.
warning
(
u"Tried to set
%
s's acl_level for
%
s (
%
s->
%
s) by
%
s."
,
unicode
(
whom
),
unicode
(
self
.
instance
),
old_level
,
new_level
,
unicode
(
user
))
def
set_or_remove_levels
(
self
):
for
key
,
value
in
self
.
request
.
POST
.
items
():
m
=
re
.
match
(
'(perm|remove)-([ug])-(
\
d+)'
,
key
)
self
.
__handle_user_assignment
(
request
)
def
__get_removes
(
self
,
request
):
removes
=
{
"u"
:
[],
"g"
:
[],
}
for
key
,
value
in
request
.
POST
.
items
():
m
=
re
.
match
(
'remove-([ug])-(.+)'
,
key
)
if
m
:
cmd
,
typ
,
id
=
m
.
groups
()
if
cmd
==
'remove'
:
value
=
None
entity
=
{
'u'
:
User
,
'g'
:
Group
}[
typ
]
.
objects
.
get
(
id
=
id
)
self
.
set_level
(
entity
,
value
)
def
add_levels
(
self
):
name
=
self
.
request
.
POST
.
get
(
'name'
,
None
)
level
=
self
.
request
.
POST
.
get
(
'level'
,
None
)
if
not
name
or
not
level
:
return
try
:
entity
=
search_user
(
name
)
if
self
.
instance
.
object_level_set
.
filter
(
users__in
=
[
entity
]):
messages
.
warning
(
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
)
t
,
name
=
m
.
groups
()
removes
[
t
]
.
append
(
name
)
return
removes
def
__remove_user
(
self
,
request
,
member_id
):
template
=
self
.
get_object
()
glance
=
self
.
__get_glance_admin_client
(
request
.
user
.
tenant_id
)
glance
.
image_members
.
delete
(
template
.
image_id
,
member_id
)
def
__handle_removes
(
self
,
request
):
removes
=
self
.
__get_removes
(
request
)
for
member_id
in
removes
[
'u'
]:
self
.
__remove_user
(
request
,
member_id
)
messages
.
success
(
request
,
_
(
"Successfully removed user"
))
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
instanc
e
=
self
.
get_object
()
self
.
is_owner
=
self
.
instance
.
has_level
(
request
.
user
,
'owner'
)
self
.
acl_data
=
(
self
.
instance
.
get_users_with_level
()
+
self
.
instance
.
get_groups_with_level
()
)
self
.
set_or_remove_levels
(
)
self
.
add_levels
()
return
redirect
(
"
%
s#access"
%
self
.
instanc
e
.
get_absolute_url
())
templat
e
=
self
.
get_object
()
openstack_api
.
glance
.
image_update
(
request
,
template
.
image_id
,
visibility
=
"shared"
)
self
.
__handle_assignments
(
request
)
self
.
__handle_removes
(
request
)
return
redirect
(
"
%
s#access"
%
templat
e
.
get_absolute_url
())
class
GraphMixin
(
object
):
...
...
circle/openstack_api/keystone.py
View file @
4c29a7bb
...
...
@@ -593,7 +593,7 @@ def group_delete(request, group_id):
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
=
[]
kwargs
=
{
"domain"
:
domain
,
...
...
@@ -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
):
manager
=
keystoneclient
(
request
,
admin
=
True
)
.
roles
manager
=
keystoneclient
(
request
)
.
roles
return
manager
.
list
(
group
=
group
,
domain
=
domain
,
project
=
project
)
...
...
circle/vm/models/instance.py
View file @
4c29a7bb
...
...
@@ -119,6 +119,15 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
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
):
"""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