Commit 7cb09284 by Szabolcs Gelencser

Trying integration

parent aa92a44d
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$/circle" />
<option name="settingsModule" value="circle/settings/local.py" />
<option name="manageScript" value="manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 2.7 (cloud)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/circle/network/templates" />
</list>
</option>
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal">
<data-source name="Django default" uuid="e21df92f-3677-4e60-b52d-95bf8bd42c58">
<database-info product="" version="" jdbc-version="" driver-name="" driver-version="" />
<auth-required>false</auth-required>
<first-sync>true</first-sync>
</data-source>
<data-source name="db.sqlite3" uuid="4255fd52-caf4-4a1f-903a-d9a435b0487e">
<database-info product="SQLite" version="3.16.1" jdbc-version="2.1" driver-name="SQLiteJDBC" driver-version="native" />
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
<secret-storage>master_key</secret-storage>
<auth-required>false</auth-required>
<introspection-schemas>*:main</introspection-schemas>
</data-source>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="Django default" uuid="e21df92f-3677-4e60-b52d-95bf8bd42c58">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/circle/circle/settings/base.py</remarks>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/circle/circle/db.sqlite3</jdbc-url>
<driver-properties>
<property name="enable_load_extension" value="true" />
</driver-properties>
</data-source>
<data-source source="LOCAL" name="db.sqlite3" uuid="4255fd52-caf4-4a1f-903a-d9a435b0487e">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/circle/circle/db.sqlite3</jdbc-url>
<driver-properties>
<property name="enable_load_extension" value="true" />
</driver-properties>
</data-source>
</component>
</project>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
<list size="2">
<item index="0" class="java.lang.String" itemvalue="2.7" />
<item index="1" class="java.lang.String" itemvalue="3.6" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="salt" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 2.7 (cloud)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="2" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/cloud.iml" filepath="$PROJECT_DIR$/.idea/cloud.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions">
<TaskOptions isEnabled="true">
<option name="arguments" value="--no-color $FileName$" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="less" />
<option name="immediateSync" value="true" />
<option name="name" value="Less" />
<option name="output" value="$FileNameWithoutExtension$.css" />
<option name="outputFilters">
<array>
<FilterInfo>
<option name="description" value="" />
<option name="name" value="" />
<option name="regExp" value="$MESSAGE$\Q in \E$FILE_PATH$\Q on line \E$LINE$\Q, column \E$COLUMN$" />
</FilterInfo>
</array>
</option>
<option name="outputFromStdout" value="true" />
<option name="program" value="/usr/local/bin/lessc" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="false" />
<option name="workingDir" value="$FileDir$" />
<envs />
</TaskOptions>
</component>
</project>
\ No newline at end of file
......@@ -17,7 +17,7 @@
import logging
from django.contrib.auth.models import User, Group
from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation
)
......@@ -26,6 +26,8 @@ from django.db.models import (
ManyToManyField, ForeignKey, CharField, Model, IntegerField, Q
)
from openstack_auth.user import User
logger = logging.getLogger(__name__)
......@@ -160,6 +162,8 @@ class AclBase(Model):
i.save()
def has_level(self, user, level, group_also=True):
return True
logger.debug('%s.has_level(%s, %s, %s) called',
*[unicode(p) for p in [self, user, level, group_also]])
if user is None or not user.is_authenticated():
......
......@@ -17,6 +17,7 @@
"""Common settings and globals."""
# flake8: noqa
import os
from os import environ
from os.path import (abspath, basename, dirname, join, normpath, isfile,
exists, expanduser)
......@@ -35,7 +36,6 @@ from json import loads
# Normally you should not import ANYTHING from Django directly
# into your settings, but ImproperlyConfigured is an exception.
def get_env_variable(var_name, default=None):
""" Get the environment variable or return exception/default """
try:
......@@ -91,14 +91,17 @@ EMAIL_SUBJECT_PREFIX = get_env_variable('DJANGO_SUBJECT_PREFIX', '[CIRCLE] ')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.' +
get_env_variable('DJANG_DB_TYPE', 'postgresql_psycopg2'),
'NAME': get_env_variable('DJANGO_DB_NAME', 'circle'),
'USER': get_env_variable('DJANGO_DB_USER', 'circle'),
'PASSWORD': get_env_variable('DJANGO_DB_PASSWORD'),
'HOST': get_env_variable('DJANGO_DB_HOST', ''),
'PORT': get_env_variable('DJANGO_DB_PORT', ''),
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
# 'default': {
# 'ENGINE': 'django.db.backends.mysql',
# 'NAME': 'circlestack',
# 'USER': 'circlestack',
# 'PASSWORD': 'asdQWE123',
# 'HOST': '10.10.20.33',
# 'PORT': '',
# }
}
########## END DATABASE CONFIGURATION
......@@ -289,14 +292,12 @@ TEMPLATES = [{
'APP_DIRS': True,
'OPTIONS': {
'context_processors': (
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request',
'dashboard.context_processors.notifications',
'dashboard.context_processors.extract_settings',
'dashboard.context_processors.broadcast_messages',
......@@ -355,7 +356,6 @@ THIRD_PARTY_APPS = (
'sizefield',
'taggit',
'statici18n',
'django_sshkey',
'pipeline',
)
......@@ -372,12 +372,17 @@ LOCAL_APPS = (
'acl',
'monitor',
'request',
'openstack_auth',
'openstack_api'
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
########## END APP CONFIGURATION
AUTHENTICATION_BACKENDS = ('openstack_auth.backend.KeystoneBackend',)
AUTHENTICATION_URLS = ['openstack_auth.urls']
AUTH_USER_MODEL = 'openstack_auth.User'
########## LOGGING CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
......@@ -431,7 +436,7 @@ LOGGING = {
WSGI_APPLICATION = '%s.wsgi.application' % SITE_NAME
########## END WSGI CONFIGURATION
FIREWALL_SETTINGS = loads(get_env_variable('DJANGO_FIREWALL_SETTINGS'))
FIREWALL_SETTINGS = {} #loads(get_env_variable('DJANGO_FIREWALL_SETTINGS'))
CRISPY_TEMPLATE_PACK = 'bootstrap3'
......@@ -454,77 +459,6 @@ CACHES = {
}
if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
try:
from shutil import which # python >3.4
except ImportError:
from shutilwhich import which
from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
INSTALLED_APPS += (
'djangosaml2',
)
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'common.backends.Saml2Backend',
)
remote_metadata = join(SITE_ROOT, 'remote_metadata.xml')
if not isfile(remote_metadata):
raise ImproperlyConfigured('Download SAML2 metadata to %s' %
remote_metadata)
required_attrs = loads(get_env_variable('DJANGO_SAML_REQUIRED',
'["uid"]'))
optional_attrs = loads(get_env_variable('DJANGO_SAML_OPTIONAL',
'["mail", "cn", "sn"]'))
SAML_CONFIG = {
'xmlsec_binary': which('xmlsec1'),
'entityid': DJANGO_URL + 'saml2/metadata/',
'attribute_map_dir': join(SITE_ROOT, 'attribute-maps'),
'service': {
'sp': {
'name': SITE_NAME,
'endpoints': {
'assertion_consumer_service': [
(DJANGO_URL + 'saml2/acs/', BINDING_HTTP_POST),
],
'single_logout_service': [
(DJANGO_URL + 'saml2/ls/', BINDING_HTTP_REDIRECT),
],
},
'required_attributes': required_attrs,
'optional_attributes': optional_attrs,
},
},
'metadata': {'local': [remote_metadata], },
'key_file': join(SITE_ROOT, 'samlcert.key'), # private part
'cert_file': join(SITE_ROOT, 'samlcert.pem'), # public part
'encryption_keypairs': [{
'key_file': join(SITE_ROOT, 'samlcert.key'), # private part
'cert_file': join(SITE_ROOT, 'samlcert.pem'), # public part
}]
}
try:
SAML_CONFIG += loads(get_env_variable('DJANGO_SAML_SETTINGS'))
except ImproperlyConfigured:
pass
SAML_CREATE_UNKNOWN_USER = True
SAML_ATTRIBUTE_MAPPING = loads(get_env_variable(
'DJANGO_SAML_ATTRIBUTE_MAPPING',
'{"mail": ["email"], "sn": ["last_name"], '
'"uid": ["username"], "cn": ["first_name"]}'))
SAML_GROUP_ATTRIBUTES = get_env_variable(
'DJANGO_SAML_GROUP_ATTRIBUTES', '').split(',')
SAML_GROUP_OWNER_ATTRIBUTES = get_env_variable(
'DJANGO_SAML_GROUP_OWNER_ATTRIBUTES', '').split(',')
SAML_CREATE_UNKNOWN_USER = True
if get_env_variable('DJANGO_SAML_ORG_ID_ATTRIBUTE', False) is not False:
SAML_ORG_ID_ATTRIBUTE = get_env_variable(
'DJANGO_SAML_ORG_ID_ATTRIBUTE')
SAML_MAIN_ATTRIBUTE_MAX_LENGTH = int(get_env_variable(
"DJANGO_SAML_MAIN_ATTRIBUTE_MAX_LENGTH", 0))
LOGIN_REDIRECT_URL = "/"
......@@ -570,8 +504,24 @@ STORE_CLIENT_KEY = get_env_variable("STORE_CLIENT_KEY", "")
STORE_CLIENT_CERT = get_env_variable("STORE_CLIENT_CERT", "")
STORE_URL = get_env_variable("STORE_URL", "")
SESSION_COOKIE_NAME = "csessid%x" % (((getnode() // 139) ^
(getnode() % 983)) & 0xffff)
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
SESSION_COOKIE_HTTPONLY = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_SECURE = False
# SESSION_TIMEOUT is a method to supersede the token timeout with a shorter
# horizon session timeout (in seconds). So if your token expires in 60
# minutes, a value of 1800 will log users out after 30 minutes
SESSION_TIMEOUT = 3600
# When using cookie-based sessions, log error when the session cookie exceeds
# the following size (common browsers drop cookies above a certain size):
SESSION_COOKIE_MAX_SIZE = 4093
# when doing upgrades, it may be wise to stick to PickleSerializer
# NOTE(berendt): Check during the K-cycle if this variable can be removed.
# https://bugs.launchpad.net/horizon/+bug/1349463
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
MAX_NODE_RAM = get_env_variable("MAX_NODE_RAM", 1024)
......@@ -584,6 +534,4 @@ BLACKLIST_PASSWORD = get_env_variable("BLACKLIST_PASSWORD", "")
BLACKLIST_HOOK_URL = get_env_variable("BLACKLIST_HOOK_URL", "")
REQUEST_HOOK_URL = get_env_variable("REQUEST_HOOK_URL", "")
SSHKEY_EMAIL_ADD_KEY = False
TWO_FACTOR_ISSUER = get_env_variable("TWO_FACTOR_ISSUER", "CIRCLE")
......@@ -57,17 +57,15 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '127.0.0.1:11211',
'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
}
}
########## END CACHE CONFIGURATION
########## ROSETTA CONFIGURATION
INSTALLED_APPS += (
'rosetta',
)
# INSTALLED_APPS += (
# 'rosetta',
# )
########## END ROSETTA CONFIGURATION
......@@ -117,3 +115,5 @@ PIPELINE["COMPILERS"] = (
ADMIN_ENABLED = True
ALLOWED_HOSTS = ['*']
OPENSTACK_KEYSTONE_URL="http://10.10.20.30:5000"
\ No newline at end of file
......@@ -16,81 +16,73 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.conf.urls import include, url
from django.views.generic import TemplateView
from django.conf import settings
from django.contrib import admin
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.contrib.auth.views import (
password_reset_confirm, password_reset
)
from circle.settings.base import get_env_variable
from dashboard.views import (
CircleLoginView, HelpView, ResizeHelpView, TwoFactorLoginView
)
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
from firewall.views import add_blacklist_item
from openstack_auth import utils
from openstack_auth.views import login
admin.autodiscover()
utils.patch_middleware_get_user()
urlpatterns = [
url(r'^$', lambda x: redirect(reverse("dashboard.index"))),
url(r'^network/', include('network.urls')),
url(r'^blacklist-add/', add_blacklist_item),
# url(r'^network/', include('network.urls')),
# url(r'^blacklist-add/', add_blacklist_item),
url(r'^dashboard/', include('dashboard.urls')),
url(r'^request/', include('request.urls')),
# django/contrib/auth/urls.py (care when new version)
url((r'^accounts/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/'
r'(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$'),
password_reset_confirm,
{'set_password_form': CircleSetPasswordForm},
name='accounts.password_reset_confirm'
),
url(r'^accounts/password/reset/$', password_reset,
{'password_reset_form': CirclePasswordResetForm},
name="accounts.password-reset",
),
url(r'^accounts/login/?$', CircleLoginView.as_view(),
# url((r'^accounts/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/'
# r'(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$'),
# password_reset_confirm,
# {'set_password_form': CircleSetPasswordForm},
# name='accounts.password_reset_confirm'
# ),
# url(r'^accounts/password/reset/$', password_reset,
# {'password_reset_form': CirclePasswordResetForm},
# name="accounts.password-reset",
# ),
url(r'^accounts/login/?$', login,
name="accounts.login"),
url(r'^accounts/', include('django.contrib.auth.urls')),
url(r'^two-factor-login/$', TwoFactorLoginView.as_view(),
name="two-factor-login"),
url(r'^info/help/$', HelpView.as_view(template_name="info/help.html"),
name="info.help"),
url(r'^info/policy/$',
TemplateView.as_view(template_name="info/policy.html"),
name="info.policy"),
url(r'^info/legal/$',
TemplateView.as_view(template_name="info/legal.html"),
name="info.legal"),
url(r'^info/support/$',
TemplateView.as_view(template_name="info/support.html"),
name="info.support"),
url(r'^info/resize-how-to/$', ResizeHelpView.as_view(),
name="info.resize"),
# url(r'^two-factor-login/$', TwoFactorLoginView.as_view(),
# name="two-factor-login"),
#
# url(r'^info/help/$', HelpView.as_view(template_name="info/help.html"),
# name="info.help"),
# url(r'^info/policy/$',
# TemplateView.as_view(template_name="info/policy.html"),
# name="info.policy"),
# url(r'^info/legal/$',
# TemplateView.as_view(template_name="info/legal.html"),
# name="info.legal"),
# url(r'^info/support/$',
# TemplateView.as_view(template_name="info/support.html"),
# name="info.support"),
# url(r'^info/resize-how-to/$', ResizeHelpView.as_view(),
# name="info.resize"),
]
if 'rosetta' in settings.INSTALLED_APPS:
urlpatterns += [
url(r'^rosetta/', include('rosetta.urls')),
]
if settings.ADMIN_ENABLED:
urlpatterns += [
url(r'^admin/', include(admin.site.urls)),
]
if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
urlpatterns += [
url(r'^saml2/', include('djangosaml2.urls')),
]
# if 'rosetta' in settings.INSTALLED_APPS:
# urlpatterns += [
# url(r'^rosetta/', include('rosetta.urls')),
# ]
#
# if settings.ADMIN_ENABLED:
# urlpatterns += [
# url(r'^admin/', include(admin.site.urls)),
# ]
#
#
# if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
# urlpatterns += [
# url(r'^saml2/', include('djangosaml2.urls')),
# ]
handler500 = 'common.views.handler500'
handler403 = 'common.views.handler403'
......@@ -25,7 +25,6 @@ from time import time
from warnings import warn
from django.contrib import messages
from django.contrib.auth.models import User
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.core.serializers.json import DjangoJSONEncoder
......@@ -42,6 +41,8 @@ from jsonfield import JSONField
from manager.mancelery import celery
from model_utils.models import TimeStampedModel
from openstack_auth.user import User
logger = getLogger(__name__)
......
......@@ -39,8 +39,8 @@ class GroupProfileInline(contrib.admin.TabularInline):
UserAdmin.inlines = (ProfileInline, CommandInline, )
GroupAdmin.inlines = (GroupProfileInline, )
contrib.admin.site.unregister(User)
contrib.admin.site.register(User, UserAdmin)
# contrib.admin.site.unregister(User)
# contrib.admin.site.register(User, UserAdmin)
contrib.admin.site.unregister(Group)
contrib.admin.site.register(Group, GroupAdmin)
......
......@@ -21,10 +21,10 @@ from .models import Message
def notifications(request):
count = (request.user.notification_set.filter(status="new").count()
if request.user.is_authenticated() else None)
# count = (request.user.notification_set.filter(status="new").count()
# if request.user.is_authenticated() else None)
return {
'NEW_NOTIFICATIONS_COUNT': count
'NEW_NOTIFICATIONS_COUNT': 0#count
}
......
......@@ -50,7 +50,6 @@ from django.utils.translation import ugettext_lazy as _
from sizefield.widgets import FileSizeWidget
from django.core.urlresolvers import reverse_lazy
from django_sshkey.models import UserKey
from firewall.models import Vlan, Host
from vm.models import (
InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance
......@@ -64,8 +63,9 @@ from django.utils.translation import string_concat
from .validators import domain_validator
from dashboard.models import ConnectCommand, create_profile
from dashboard.models import ConnectCommand
from openstack_auth.user_key import UserKey
LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")"))
for l in LANGUAGES)
......@@ -1290,7 +1290,6 @@ class UserCreationForm(OrgUserCreationForm):
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
create_profile(user)
user.groups.add(*self.cleaned_data["groups"])
return user
......@@ -1404,43 +1403,43 @@ class ConnectCommandForm(forms.ModelForm):
return super(ConnectCommandForm, self).clean()
class TraitsForm(forms.ModelForm):
class Meta:
model = Instance
fields = ('req_traits', )
@property
def helper(self):
helper = FormHelper()
helper.form_show_labels = False
helper.form_action = reverse_lazy("dashboard.views.vm-traits",
kwargs={'pk': self.instance.pk})
helper.add_input(Submit("submit", _("Save"),
css_class="btn btn-success", ))
return helper
class RawDataForm(forms.ModelForm):
raw_data = forms.CharField(validators=[domain_validator],
widget=forms.Textarea(attrs={'rows': 5}),
required=False)
class Meta:
model = Instance
fields = ('raw_data', )
@property
def helper(self):
helper = FormHelper()
helper.form_show_labels = False
helper.form_action = reverse_lazy("dashboard.views.vm-raw-data",
kwargs={'pk': self.instance.pk})
helper.add_input(Submit("submit", _("Save"),
css_class="btn btn-success",
css_id="submit-password-button"))
return helper
# class TraitsForm(forms.ModelForm):
#
# class Meta:
# model = Instance
# fields = ('req_traits', )
#
# @property
# def helper(self):
# helper = FormHelper()
# helper.form_show_labels = False
# helper.form_action = reverse_lazy("dashboard.views.vm-traits",
# kwargs={'pk': self.instance.pk})
# helper.add_input(Submit("submit", _("Save"),
# css_class="btn btn-success", ))
# return helper
#
#
# class RawDataForm(forms.ModelForm):
# raw_data = forms.CharField(validators=[domain_validator],
# widget=forms.Textarea(attrs={'rows': 5}),
# required=False)
#
# class Meta:
# model = Instance
# fields = ('raw_data', )
#
# @property
# def helper(self):
# helper = FormHelper()
# helper.form_show_labels = False
# helper.form_action = reverse_lazy("dashboard.views.vm-raw-data",
# kwargs={'pk': self.instance.pk})
# helper.add_input(Submit("submit", _("Save"),
# css_class="btn btn-success",
# css_id="submit-password-button"))
# return helper
#
class GroupPermissionForm(forms.ModelForm):
permissions = forms.ModelMultipleChoiceField(
......
......@@ -23,7 +23,7 @@ from hashlib import md5
from logging import getLogger
from django.conf import settings
from django.contrib.auth.models import User, Group
from django.contrib.auth.models import Group
from django.contrib.auth.signals import user_logged_in
from django.core.urlresolvers import reverse
from django.db.models import (
......@@ -35,7 +35,6 @@ from django.templatetags.static import static
from django.utils import timezone
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _
from django_sshkey.models import UserKey
from django.core.exceptions import ObjectDoesNotExist
from sizefield.models import FileSizeField
......@@ -53,6 +52,8 @@ from vm.models.instance import ACCESS_METHODS
from .store_api import Store, NoStoreException, NotOkException, Timeout
from .validators import connect_command_template_validator
from openstack_auth.user import User
logger = getLogger(__name__)
......@@ -324,25 +325,6 @@ def get_or_create_profile(self):
Group.profile = property(get_or_create_profile)
def create_profile(user):
if not user.pk:
return False
profile, created = Profile.objects.get_or_create(user=user)
try:
Store(user).create_user(profile.smb_password, None, profile.disk_quota)
except:
logger.exception("Can't create user %s", unicode(user))
return created
def create_profile_hook(sender, user, request, **kwargs):
return create_profile(user)
user_logged_in.connect(create_profile_hook)
if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
logger.debug("Register save_org_id to djangosaml2 pre_user_save")
from djangosaml2.signals import pre_user_save
......@@ -436,8 +418,8 @@ def update_store_keys(sender, **kwargs):
logger.critical("Store is not accepting connections.")
post_save.connect(update_store_keys, sender=UserKey)
post_delete.connect(update_store_keys, sender=UserKey)
# post_save.connect(update_store_keys, sender=UserKey)
# post_delete.connect(update_store_keys, sender=UserKey)
def add_ssh_keys(sender, **kwargs):
......@@ -470,5 +452,5 @@ def del_ssh_keys(sender, **kwargs):
logger.info("%s has no agent running", i)
post_save.connect(add_ssh_keys, sender=UserKey)
pre_delete.connect(del_ssh_keys, sender=UserKey)
# post_save.connect(add_ssh_keys, sender=UserKey)
# pre_delete.connect(del_ssh_keys, sender=UserKey)
......@@ -25,12 +25,12 @@ from django_tables2 import Table, A
from django_tables2.columns import (
TemplateColumn, Column, LinkColumn, BooleanColumn
)
from django_sshkey.models import UserKey
from storage.models import Disk
from vm.models import Node, InstanceTemplate, Lease
from dashboard.models import ConnectCommand, Message
from openstack_auth.user_key import UserKey
class FileSizeColumn(Column):
def render(self, value):
......
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title-page %}{% trans "Virtual machines" %}{% endblock %}
{% block content %}
<form method="POST">
{% csrf_token %}
<div class="input-group">
<span class="input-group-addon">
<i class="fa fa-user"></i>
</span>
<input type="text" name="username" class="form-control">
</div>
<div class="input-group">
<span class="input-group-addon">
<i class="fa fa-lock"></i>
</span>
<input type="password" name="password" class="form-control">
</div>
<button type="submit" class="btn btn-success">Sign in</button>
</form>
{% endblock %}
\ No newline at end of file
......@@ -80,10 +80,10 @@
</div> <!-- /container -->
<footer>
<a href="{% url "info.legal" %}">{% trans "Legal notice" %}</a> |
<a href="{% url "info.policy" %}">{% trans "Policy" %}</a> |
<a href="{% url "info.help" %}">{% trans "Help" %}</a> |
<a href="{% url "info.support" %}">{% trans "Support" %}</a>
{# <a href="{% url "info.legal" %}">{% trans "Legal notice" %}</a> |#}
{# <a href="{% url "info.policy" %}">{% trans "Policy" %}</a> |#}
{# <a href="{% url "info.help" %}">{% trans "Help" %}</a> |#}
{# <a href="{% url "info.support" %}">{% trans "Support" %}</a>#}
<span class="pull-right">{{ COMPANY_NAME }}</span>
</footer>
......
......@@ -11,7 +11,7 @@
{% block navbar %}
{% if request.user.is_authenticated and request.user.pk and not request.token_user %}
{% if view.request.user.is_authenticated and view.request.user.pk and not view.request.token_user %}
<span id="user-options" data-desktop_notifications="{{ request.user.profile.desktop_notifications }}"><span>
......@@ -56,45 +56,45 @@
</a>
</li>
{% endif %}
<li>
<a href="{% url "dashboard.views.profile-preferences" %}">
<i class="fa fa-user"></i>
{% include "dashboard/_display-name.html" with user=request.user show_org=True %}
</a>
</li>
{# <li>#}
{# <a href="{% url "dashboard.views.profile-preferences" %}">#}
{# <i class="fa fa-user"></i>#}
{# {% include "dashboard/_display-name.html" with user=request.user show_org=True %}#}
{# </a>#}
{# </li>#}
<li>
<a href="{% url "logout" %}?next={% url "login" %}">
<i class="fa fa-sign-out"></i> {% trans "Log out" %}
</a>
</li>
<li class="visible-xs">
<a href="{% url "dashboard.views.notifications" %}">
{% trans "Notifications" %}
{% if NEW_NOTIFICATIONS_COUNT > 0 %}
<span class="badge badge-pulse">{{ NEW_NOTIFICATIONS_COUNT }}</span>
{% endif %}
</a>
</li>
<li class="dropdown hidden-xs" id="notification-button">
<a href="{% url "dashboard.views.notifications" %}"
class="dropdown-toggle" data-toggle="dropdown"
id="notification_count" data-notifications="{{ NEW_NOTIFICATIONS_COUNT }}">
{% trans "Notifications" %}
{% if NEW_NOTIFICATIONS_COUNT > 0 %}
<span class="badge badge-pulse">
{{ NEW_NOTIFICATIONS_COUNT }}
</span>
{% endif %}
</a>
<ul class="dropdown-menu" id="notification-messages">
<li>{% trans "Loading..." %}</li>
</ul>
</li>
<li class="hidden-xs">
<a href="{% url "dashboard.views.profile-preferences" %}">
<img class="profile-avatar" src="{{ request.user.profile.get_avatar_url }}" />
</a>
</li>
{# <li class="visible-xs">#}
{# <a href="{% url "dashboard.views.notifications" %}">#}
{# {% trans "Notifications" %}#}
{# {% if NEW_NOTIFICATIONS_COUNT > 0 %}#}
{# <span class="badge badge-pulse">{{ NEW_NOTIFICATIONS_COUNT }}</span>#}
{# {% endif %}#}
{# </a>#}
{# </li>#}
{# <li class="dropdown hidden-xs" id="notification-button">#}
{# <a href="{% url "dashboard.views.notifications" %}"#}
{# class="dropdown-toggle" data-toggle="dropdown"#}
{# id="notification_count" data-notifications="{{ NEW_NOTIFICATIONS_COUNT }}">#}
{# {% trans "Notifications" %}#}
{# {% if NEW_NOTIFICATIONS_COUNT > 0 %}#}
{# <span class="badge badge-pulse">#}
{# {{ NEW_NOTIFICATIONS_COUNT }}#}
{# </span>#}
{# {% endif %}#}
{# </a>#}
{# <ul class="dropdown-menu" id="notification-messages">#}
{# <li>{% trans "Loading..." %}</li>#}
{# </ul>#}
{# </li>#}
{# <li class="hidden-xs">#}
{# <a href="{% url "dashboard.views.profile-preferences" %}">#}
{# <img class="profile-avatar" src="{{ request.user.profile.get_avatar_url }}" />#}
{# </a>#}
{# </li>#}
</ul>
{% else %}
......
......@@ -33,8 +33,7 @@
{{ i.name }}
</span>
<small class="text-muted index-vm-list-host">
{% if i.owner == request.user %}{{ i.short_hostname }}
{% else %}{{i.owner.profile.get_display_name}}{% endif %}
{{ i.short_hostname }}
</small>
<div class="pull-right dashboard-vm-favourite" data-vm="{{ i.pk }}">
{% if i.fav %}
......
......@@ -4,56 +4,50 @@
{% block title-page %}{% trans "Index" %}{% endblock %}
{% block extra_link_2 %}
<link rel="search"
type="application/opensearchdescription+xml"
href="{% url "dashboard.views.vm-opensearch" %}"
title="{% blocktrans with name=COMPANY_NAME %}{{name}} virtual machines{% endblocktrans %}" />
{% endblock %}
{#{% block extra_link_2 %}#}
{#<link rel="search"#}
{# type="application/opensearchdescription+xml"#}
{# href="{% url "dashboard.views.vm-opensearch" %}"#}
{# title="{% blocktrans with name=COMPANY_NAME %}{{name}} virtual machines{% endblocktrans %}" />#}
{#{% endblock %}#}
{% block content %}
<div class="body-content dashboard-index">
<div class="row">
{% if perms.vm %}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-vm.html" %}
</div>
{% else %}
<div class="alert alert-info">
{% trans "You have no permission to start or manage virtual machines." %}
</div>
{% endif %}
{% if perms.auth %}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-groups.html" %}
</div>
{% endif %}
{% if not no_store %}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/store/index-files.html" %}
</div>
{% endif %}
{% if perms.vm.create_template %}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-templates.html" %}
</div>
{% endif %}
{% if perms.vm.view_statistics %}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-nodes.html" %}
</div>
{% endif %}
{% if perms.auth.change_user %}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-users.html" %}
</div>
{% endif %}
{# {% if perms.auth %}#}
{# <div class="col-lg-4 col-sm-6">#}
{# {% include "dashboard/index-groups.html" %}#}
{# </div>#}
{# {% endif %}#}
{##}
{# {% if not no_store %}#}
{# <div class="col-lg-4 col-sm-6">#}
{# {% include "dashboard/store/index-files.html" %}#}
{# </div>#}
{# {% endif %}#}
{##}
{# {% if perms.vm.create_template %}#}
{# <div class="col-lg-4 col-sm-6">#}
{# {% include "dashboard/index-templates.html" %}#}
{# </div>#}
{# {% endif %}#}
{##}
{# {% if perms.vm.view_statistics %}#}
{# <div class="col-lg-4 col-sm-6">#}
{# {% include "dashboard/index-nodes.html" %}#}
{# </div>#}
{# {% endif %}#}
{##}
{# {% if perms.auth.change_user %}#}
{# <div class="col-lg-4 col-sm-6">#}
{# {% include "dashboard/index-users.html" %}#}
{# </div>#}
{# {% endif %}#}
</div>
</div>
{% endblock %}
......@@ -217,11 +217,6 @@
<i class="fa fa-desktop fa-2x"></i><br>
{% trans "Console" %}</a></li>
<li>
<a href="#access" data-toggle="pill" data-target="#_access" class="text-center">
<i class="fa fa-group fa-2x"></i><br>
{% trans "Access" %}</a>
</li>
<li>
<a href="#network" data-toggle="pill" data-target="#_network" class="text-center">
<i class="fa fa-globe fa-2x"></i><br>
{% trans "Network" %}</a>
......
......@@ -11,9 +11,9 @@
{% endif %}
{% if user == instance.owner or user.is_superuser %}
<span class="operation-wrapper">
<a href="{% url "dashboard.views.vm-transfer-ownership" instance.pk %}"
class="btn btn-link operation">{% trans "Transfer ownership..." %}</a>
</span>
{# <a href="{% url "dashboard.views.vm-transfer-ownership" instance.pk %}"#}
{# class="btn btn-link operation">{% trans "Transfer ownership..." %}</a>#}
{# </span>#}
{% endif %}
</p>
<h3>{% trans "Permissions"|capfirst %}</h3>
......
......@@ -56,7 +56,7 @@
<div id="home_expiration_and_lease">
<h4>
{% trans "Expiration" %}
{% if instance.is_expiring %}<i class="fa fa-warning-sign text-danger"></i>{% endif %}
{# {% if instance.is_expiring %}<i class="fa fa-warning-sign text-danger"></i>{% endif %}#}
<span id="vm-details-renew-op">
{% with op=op.renew %}{% if op %}
<a href="{{op.get_url}}" class="btn btn-xs operation operation-{{ op.op }}
......
......@@ -3,16 +3,16 @@
# __all__ = [ ]
from group import *
from index import *
from node import *
from store import *
from template import *
from user import *
# from group import *
from index import IndexView
# from node import *
# from store import *
# from template import *
# from user import *
from util import *
from vm import *
from graph import *
from storage import *
from request import *
from message import *
# from vm import *
# from graph import *
# from storage import *
# from request import *
# from message import *
from autocomplete import *
......@@ -18,17 +18,13 @@ from __future__ import unicode_literals, absolute_import
import logging
from django.core.cache import cache
from django.core.urlresolvers import reverse
from braces.views import LoginRequiredMixin
from dashboard.models import GroupProfile
from django.conf import settings
from django.contrib.auth.models import Group, User
from django.core.cache import cache
from django.views.generic import TemplateView
from braces.views import LoginRequiredMixin
from dashboard.models import GroupProfile
from vm.models import Instance, Node, InstanceTemplate
from dashboard.views.vm import vm_ops
from ..store_api import Store
......@@ -42,25 +38,32 @@ class IndexView(LoginRequiredMixin, TemplateView):
user = self.request.user
context = super(IndexView, self).get_context_data(**kwargs)
# instances
favs = Instance.objects.filter(favourite__user=self.request.user)
instances = Instance.get_objects_with_level(
'user', user, disregard_superuser=True).filter(destroyed_at=None)
display = list(favs) + list(set(instances) - set(favs))
for d in display:
d.fav = True if d in favs else False
instances = Instance.list_from_os(self.request)
context.update({
'instances': display[:5],
'more_instances': instances.count() - len(instances[:5])
'instances': instances[:5],
'more_instances': len(instances) - len(instances[:5])
})
running = instances.filter(status='RUNNING')
stopped = instances.exclude(status__in=('RUNNING', 'NOSTATE'))
# instances
# favs = Instance.objects.filter(favourite__user=self.request.user)
# instances = Instance.get_objects_with_level(
# 'user', user, disregard_superuser=True).filter(destroyed_at=None)
# display = list(favs) + list(set(instances) - set(favs))
# for d in display:
# d.fav = True if d in favs else False
# context.update({
# 'instances': display[:5],
# 'more_instances': instances.count() - len(instances[:5])
# })
running = instances#instances.filter(status='RUNNING')
stopped = []#instances.exclude(status__in=('RUNNING', 'NOSTATE'))
context.update({
'running_vms': running[:20],
'running_vm_num': running.count(),
'stopped_vm_num': stopped.count()
'running_vm_num': len(running),
'stopped_vm_num': len(stopped)
})
# nodes
......@@ -123,29 +126,29 @@ class IndexView(LoginRequiredMixin, TemplateView):
return context
class HelpView(TemplateView):
def get_context_data(self, *args, **kwargs):
ctx = super(HelpView, self).get_context_data(*args, **kwargs)
operations = [(o, Instance._ops[o.op])
for o in vm_ops.values() if o.show_in_toolbar]
ctx.update({"saml": hasattr(settings, "SAML_CONFIG"),
"operations": operations,
"store": settings.STORE_URL})
return ctx
class ResizeHelpView(TemplateView):
template_name = "info/resize.html"
class OpenSearchDescriptionView(TemplateView):
template_name = "dashboard/vm-opensearch.xml"
content_type = "application/opensearchdescription+xml"
def get_context_data(self, **kwargs):
context = super(OpenSearchDescriptionView, self).get_context_data(
**kwargs)
context['url'] = self.request.build_absolute_uri(
reverse("dashboard.views.vm-list"))
return context
# class HelpView(TemplateView):
#
# def get_context_data(self, *args, **kwargs):
# ctx = super(HelpView, self).get_context_data(*args, **kwargs)
# operations = [(o, Instance._ops[o.op])
# for o in vm_ops.values() if o.show_in_toolbar]
# ctx.update({"saml": hasattr(settings, "SAML_CONFIG"),
# "operations": operations,
# "store": settings.STORE_URL})
# return ctx
#
#
# class ResizeHelpView(TemplateView):
# template_name = "info/resize.html"
#
#
# class OpenSearchDescriptionView(TemplateView):
# template_name = "dashboard/vm-opensearch.xml"
# content_type = "application/opensearchdescription+xml"
#
# def get_context_data(self, **kwargs):
# context = super(OpenSearchDescriptionView, self).get_context_data(
# **kwargs)
# context['url'] = self.request.build_absolute_uri(
# reverse("dashboard.views.vm-list"))
# return context
......@@ -39,7 +39,6 @@ from django.views.decorators.http import require_POST
from django.views.generic import (
TemplateView, View, UpdateView, CreateView, FormView
)
from django_sshkey.models import UserKey
from braces.views import LoginRequiredMixin, PermissionRequiredMixin
......@@ -59,6 +58,7 @@ from ..tables import (
from .util import saml_available, DeleteViewBase, LoginView
from openstack_auth.user_key import UserKey
logger = logging.getLogger(__name__)
......@@ -110,6 +110,7 @@ class CircleLoginView(LoginView):
def get_context_data(self, **kwargs):
ctx = super(CircleLoginView, self).get_context_data(**kwargs)
ctx.update({
'saml2': saml_available,
'og_image': (settings.DJANGO_URL.rstrip("/") +
......
......@@ -23,7 +23,6 @@ from math import ceil
import logging
import random
from django.contrib.auth.models import User
from django.db import models
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
......@@ -45,6 +44,7 @@ from firewall.tasks.remote_tasks import get_dhcp_clients
from .iptables import IptRule
from acl.models import AclBase
from openstack_auth.user import User
logger = logging.getLogger(__name__)
settings = django.conf.settings.FIREWALL_SETTINGS
......
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
# Copyright 2013 Big Switch Networks
#
# 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.
"""
Methods and interface objects used to interact with external APIs.
API method calls return objects that are in many cases objects with
attributes that are direct maps to the data returned from the API http call.
Unfortunately, these objects are also often constructed dynamically, making
it difficult to know what data is available from the API object. Because of
this, all API calls should wrap their returned object in one defined here,
using only explicitly defined attributes and/or methods.
In other words, Horizon developers not working on openstack_dashboard.api
shouldn't need to understand the finer details of APIs for
Keystone/Nova/Glance/Swift et. al.
"""
from openstack_api import base
# from openstack_api import cinder
# from openstack_api import glance
# from openstack_api import keystone
# from openstack_api import network
# from openstack_api import neutron
from openstack_api import nova
# from openstack_api import swift
__all__ = [
"base",
"cinder",
"glance",
"keystone",
"network",
"neutron",
"nova",
"swift",
]
# Copyright 2012 Nebula, Inc.
#
# 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.
"""
Drop-in replacement for django.contrib.messages which handles Horizon's
messaging needs (e.g. AJAX communication, etc.).
"""
from django.contrib import messages as _messages
from django.contrib.messages import constants
from django.utils.encoding import force_text
from django.utils.safestring import SafeData
def horizon_message_already_queued(request, message):
_message = force_text(message)
if request.is_ajax():
for tag, msg, extra in request.horizon['async_messages']:
if _message == msg:
return True
else:
for msg in _messages.get_messages(request)._queued_messages:
if msg.message == _message:
return True
return False
def add_message(request, level, message, extra_tags='', fail_silently=False):
"""Attempts to add a message to the request using the 'messages' app."""
if not horizon_message_already_queued(request, message):
if request.is_ajax():
tag = constants.DEFAULT_TAGS[level]
# if message is marked as safe, pass "safe" tag as extra_tags so
# that client can skip HTML escape for the message when rendering
if isinstance(message, SafeData):
extra_tags = extra_tags + ' safe'
request.horizon['async_messages'].append([tag,
force_text(message),
extra_tags])
else:
return _messages.add_message(request, level, message,
extra_tags, fail_silently)
def debug(request, message, extra_tags='', fail_silently=False):
"""Adds a message with the ``DEBUG`` level."""
add_message(request, constants.DEBUG, message, extra_tags=extra_tags,
fail_silently=fail_silently)
def info(request, message, extra_tags='', fail_silently=False):
"""Adds a message with the ``INFO`` level."""
add_message(request, constants.INFO, message, extra_tags=extra_tags,
fail_silently=fail_silently)
def success(request, message, extra_tags='', fail_silently=False):
"""Adds a message with the ``SUCCESS`` level."""
add_message(request, constants.SUCCESS, message, extra_tags=extra_tags,
fail_silently=fail_silently)
def warning(request, message, extra_tags='', fail_silently=False):
"""Adds a message with the ``WARNING`` level."""
add_message(request, constants.WARNING, message, extra_tags=extra_tags,
fail_silently=fail_silently)
def error(request, message, extra_tags='', fail_silently=False):
"""Adds a message with the ``ERROR`` level."""
add_message(request, constants.ERROR, message, extra_tags=extra_tags,
fail_silently=fail_silently)
# Copyright 2017 Cisco Systems
#
# 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.
import logging
LOG = logging.getLogger(__name__)
# A list of features and their supported microversions. Note that these are
# explicit functioning versions, not a range.
# There should be a minimum of two versions per feature. The first entry in
# this list should always be the lowest possible API microversion for a
# feature i.e. the version at which that feature was introduced. The second
# entry should be the current service version when the feature was added to
# horizon.
# Further documentation can be found at
# https://docs.openstack.org/horizon/latest/contributor/topics/
# microversion_support.html
MICROVERSION_FEATURES = {
"nova": {
"locked_attribute": ["2.9", "2.42"],
"instance_description": ["2.19", "2.42"],
"remote_console_mks": ["2.8", "2.53"]
},
"cinder": {
"consistency_groups": ["2.0", "3.10"],
"message_list": ["3.5", "3.29"]
}
}
# NOTE(robcresswell): Since each client implements their own wrapper class for
# API objects, we'll need to allow that to be passed in. In the future this
# should be replaced by some common handling in Oslo.
def get_microversion_for_features(service, features, wrapper_class,
min_ver, max_ver):
"""Retrieves that highest known functional microversion for features"""
if not features:
return None
# Convert a single feature string into a list for backward compatiblity.
if isinstance(features, str):
features = [features]
try:
service_features = MICROVERSION_FEATURES[service]
except KeyError:
LOG.debug("'%s' could not be found in the MICROVERSION_FEATURES dict",
service)
return None
feature_versions = set(service_features[features[0]])
for feature in features[1:]:
feature_versions &= set(service_features[feature])
if not feature_versions:
return None
# Sort version candidates from larger versins
feature_versions = sorted(feature_versions, reverse=True,
key=lambda v: [int(i) for i in v.split('.')])
for version in feature_versions:
microversion = wrapper_class(version)
if microversion.matches(min_ver, max_ver):
return microversion
return None
# Copyright 2013 NEC Corporation
#
# 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.
"""Abstraction layer for networking functionalities.
Currently Nova and Neutron have duplicated features. This API layer is
introduced to abstract the differences between them for seamless consumption by
different dashboard implementations.
"""
from openstack_dashboard.api import base
from openstack_dashboard.api import neutron
def servers_update_addresses(request, servers, all_tenants=False):
"""Retrieve servers networking information from Neutron if enabled.
Should be used when up to date networking information is required,
and Nova's networking info caching mechanism is not fast enough.
"""
# NOTE(amotoki): This check is still needed because 'instances' panel
# calls this method. We dropped security group and floating IP support
# through Nova API (due to novaclient 8.0.0 drops their supports),
# but we can still support 'Instances' panel with nova-network.
# TODO(amotoki): Nova networkinfo info caching mechanism is now fast enough
# as they are updated by Neutron via Nova event callback mechasm,
# so servers_update_addresses is no longer needed.
# We can reduce API calls by dropping it.
neutron_enabled = base.is_service_enabled(request, 'network')
if neutron_enabled:
neutron.servers_update_addresses(request, servers, all_tenants)
# Copyright 2014, Rackspace, US, Inc.
#
# 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.
"""This package holds the REST API that supports the Horizon dashboard
Javascript code.
It is not intended to be used outside of Horizon, and makes no promises of
stability or fitness for purpose outside of that scope.
It does not promise to adhere to the general OpenStack API Guidelines set out
in https://wiki.openstack.org/wiki/APIChangeGuidelines.
"""
from openstack_dashboard.api.rest import cinder
from openstack_dashboard.api.rest import config
from openstack_dashboard.api.rest import glance
from openstack_dashboard.api.rest import keystone
from openstack_dashboard.api.rest import network
from openstack_dashboard.api.rest import neutron
from openstack_dashboard.api.rest import nova
from openstack_dashboard.api.rest import policy
from openstack_dashboard.api.rest import swift
__all__ = [
'cinder',
'config',
'glance',
'keystone',
'network',
'neutron',
'nova',
'policy',
'swift',
]
# Copyright 2015 IBM Corp.
# Copyright 2015, Hewlett-Packard Development Company, L.P.
#
# 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 import settings
from django.views import generic
from openstack_dashboard import api
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils
# settings that we allow to be retrieved via REST API
# these settings are available to the client and are not secured.
# *** THEY SHOULD BE TREATED WITH EXTREME CAUTION ***
settings_required = getattr(settings, 'REST_API_REQUIRED_SETTINGS', [])
settings_additional = getattr(settings, 'REST_API_ADDITIONAL_SETTINGS', [])
settings_allowed = settings_required + settings_additional
@urls.register
class Settings(generic.View):
"""API for retrieving settings.
This API returns read-only settings values.
This configuration object can be fetched as needed.
Examples of settings: OPENSTACK_HYPERVISOR_FEATURES
"""
url_regex = r'settings/$'
SPECIALS = {
'HORIZON_IMAGES_UPLOAD_MODE': api.glance.get_image_upload_mode(),
'HORIZON_ACTIVE_IMAGE_VERSION': str(api.glance.VERSIONS.active),
'IMAGES_ALLOW_LOCATION': getattr(settings, 'IMAGES_ALLOW_LOCATION',
False)
}
@rest_utils.ajax()
def get(self, request):
plain_settings = {k: getattr(settings, k, None) for k
in settings_allowed if k not in self.SPECIALS}
plain_settings.update(self.SPECIALS)
return plain_settings
# Copyright (c) 2015 Mirantis, Inc.
#
# 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.
import json
import json.encoder as encoder
from django.utils.translation import ugettext_lazy as _
import six
class NaNJSONEncoder(json.JSONEncoder):
def __init__(self, nan_str='NaN', inf_str='1e+999', **kwargs):
self.nan_str = nan_str
self.inf_str = inf_str
super(NaNJSONEncoder, self).__init__(**kwargs)
def iterencode(self, o, _one_shot=False):
"""JSON encoder with NaN and float inf support.
The sole purpose of defining a custom JSONEncoder class is to
override floatstr() inner function, or more specifically the
representation of NaN and +/-float('inf') values in a JSON. Although
Infinity values are not supported by JSON standard, we still can
convince Javascript JSON.parse() to create a Javascript Infinity
object if we feed a token `1e+999` to it.
"""
if self.check_circular:
markers = {}
else:
markers = None
if self.ensure_ascii:
_encoder = encoder.encode_basestring_ascii
else:
_encoder = encoder.encode_basestring
# On Python 3, JSONEncoder has no more encoding attribute, it produces
# an Unicode string
if six.PY2 and self.encoding != 'utf-8':
def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
if isinstance(o, str):
o = o.decode(_encoding)
return _orig_encoder(o)
def floatstr(o, allow_nan=self.allow_nan, _repr=float.__repr__,
_inf=encoder.INFINITY, _neginf=-encoder.INFINITY):
# Check for specials. Note that this type of test is processor
# and/or platform-specific, so do tests which don't depend on the
# internals.
if o != o:
text = self.nan_str
elif o == _inf:
text = self.inf_str
elif o == _neginf:
text = '-' + self.inf_str
else:
return _repr(o)
if not allow_nan:
raise ValueError(
_("Out of range float values are not JSON compliant: %r") %
o)
return text
_iterencode = json.encoder._make_iterencode(
markers, self.default, _encoder, self.indent, floatstr,
self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, _one_shot)
return _iterencode(o, 0)
# Copyright 2015, Hewlett-Packard Development Company, L.P.
# Copyright 2016 IBM Corp.
#
# 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.
"""API for the network abstraction APIs."""
from django.views import generic
from openstack_dashboard import api
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils
@urls.register
class SecurityGroups(generic.View):
"""API for Network Abstraction
Handles differences between Nova and Neutron.
"""
url_regex = r'network/securitygroups/$'
@rest_utils.ajax()
def get(self, request):
"""Get a list of security groups.
The listing result is an object with property "items". Each item is
a security group.
Example GET:
http://localhost/api/network/securitygroups
"""
security_groups = api.neutron.security_group_list(request)
return {'items': [sg.to_dict() for sg in security_groups]}
@urls.register
class FloatingIP(generic.View):
"""API for a single floating IP address."""
url_regex = r'network/floatingip/$'
@rest_utils.ajax(data_required=True)
def post(self, request):
"""Allocate a new floating IP address.
:param pool_id: The ID of the floating IP address pool in which to
allocate the new address.
:return: JSON representation of the new floating IP address
"""
pool = request.DATA['pool_id']
result = api.neutron.tenant_floating_ip_allocate(request, pool)
return result.to_dict()
@rest_utils.ajax(data_required=True)
def patch(self, request):
"""Associate or disassociate a floating IP address.
:param address_id: The ID of the floating IP address to associate
or disassociate.
:param port_id: The ID of the port to associate.
"""
address = request.DATA['address_id']
port = request.DATA.get('port_id')
if port is None:
api.neutron.floating_ip_disassociate(request, address)
else:
api.neutron.floating_ip_associate(request, address, port)
@urls.register
class FloatingIPs(generic.View):
"""API for floating IP addresses."""
url_regex = r'network/floatingips/$'
@rest_utils.ajax()
def get(self, request):
"""Get a list of floating IP addresses.
The listing result is an object with property "items". Each item is
an extension.
Example:
http://localhost/api/network/floatingips
"""
result = api.neutron.tenant_floating_ip_list(request)
return {'items': [ip.to_dict() for ip in result]}
@urls.register
class FloatingIPPools(generic.View):
"""API for floating IP pools."""
url_regex = r'network/floatingippools/$'
@rest_utils.ajax()
def get(self, request):
"""Get a list of floating IP pools.
The listing result is an object with property "items". Each item is
an extension.
Example:
http://localhost/api/network/floatingippools
"""
result = api.neutron.floating_ip_pools_list(request)
return {'items': [p.to_dict() for p in result]}
#
# 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.views import generic
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils
from openstack_dashboard import policy
@urls.register
class Policy(generic.View):
'''API for interacting with the policy engine.'''
url_regex = r'policy/$'
@rest_utils.ajax(data_required=True)
def post(self, request):
'''Check policy rules.
Check the group of policy rules supplied in the POST
application/json object. The policy target, if specified will also be
passed in to the policy check method as well.
The action returns an object with one key: "allowed" and the value
is the result of the policy check, True or False.
'''
rules = []
try:
rules_in = request.DATA['rules']
rules = tuple([tuple(rule) for rule in rules_in])
except Exception:
raise rest_utils.AjaxError(400, 'unexpected parameter format')
policy_target = request.DATA.get('target') or {}
result = policy.check(rules, request, policy_target)
return {"allowed": result}
# Copyright 2015, Rackspace, US, Inc.
#
# 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.
"""API for the swift service."""
import os
from django import forms
from django.http import StreamingHttpResponse
from django.utils.http import urlunquote
from django.views.decorators.csrf import csrf_exempt
from django.views import generic
import six
from horizon import exceptions
from openstack_dashboard import api
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils
from openstack_dashboard.api import swift
@urls.register
class Info(generic.View):
"""API for information about the Swift installation."""
url_regex = r'swift/info/$'
@rest_utils.ajax()
def get(self, request):
"""Get information about the Swift installation."""
capabilities = api.swift.swift_get_capabilities(request)
return {'info': capabilities}
@urls.register
class Containers(generic.View):
"""API for swift container listing for an account"""
url_regex = r'swift/containers/$'
@rest_utils.ajax()
def get(self, request):
"""Get the list of containers for this account
:param prefix: container name prefix value. Named items in the
response begin with this value
TODO(neillc): Add pagination
"""
prefix = request.GET.get('prefix', None)
if prefix:
containers, has_more = api.swift.\
swift_get_containers(request, prefix=prefix)
else:
containers, has_more = api.swift.swift_get_containers(request)
containers = [container.to_dict() for container in containers]
return {'items': containers, 'has_more': has_more}
@urls.register
class Container(generic.View):
"""API for swift container level information"""
url_regex = r'swift/containers/(?P<container>[^/]+)/metadata/$'
@rest_utils.ajax()
def get(self, request, container):
"""Get the container details"""
return api.swift.swift_get_container(request, container).to_dict()
@rest_utils.ajax()
def post(self, request, container):
metadata = {}
if 'is_public' in request.DATA:
metadata['is_public'] = request.DATA['is_public']
# This will raise an exception if the container already exists
try:
api.swift.swift_create_container(request, container,
metadata=metadata)
except exceptions.AlreadyExists as e:
# 409 Conflict
return rest_utils.JSONResponse(str(e), 409)
return rest_utils.CreatedResponse(
u'/api/swift/containers/%s' % container,
)
@rest_utils.ajax()
def delete(self, request, container):
try:
api.swift.swift_delete_container(request, container)
except exceptions.Conflict as e:
# It cannot be deleted if it's not empty.
return rest_utils.JSONResponse(str(e), 409)
@rest_utils.ajax(data_required=True)
def put(self, request, container):
metadata = {'is_public': request.DATA['is_public']}
api.swift.swift_update_container(request, container, metadata=metadata)
@urls.register
class Objects(generic.View):
"""API for a list of swift objects"""
url_regex = r'swift/containers/(?P<container>[^/]+)/objects/$'
@rest_utils.ajax()
def get(self, request, container):
"""Get object information.
:param request:
:param container:
:return:
"""
path = request.GET.get('path')
if path is not None:
path = urlunquote(path)
objects = api.swift.swift_get_objects(
request,
container,
prefix=path
)
# filter out the folder from the listing if we're filtering for
# contents of a (pseudo) folder
contents = [{
'path': o.subdir if isinstance(o, swift.PseudoFolder) else o.name,
'name': o.name.split('/')[-1],
'bytes': o.bytes,
'is_subdir': isinstance(o, swift.PseudoFolder),
'is_object': not isinstance(o, swift.PseudoFolder),
'content_type': getattr(o, 'content_type', None)
} for o in objects[0] if o.name != path]
return {'items': contents}
class UploadObjectForm(forms.Form):
file = forms.FileField(required=False)
@urls.register
class Object(generic.View):
"""API for a single swift object or pseudo-folder"""
url_regex = r'swift/containers/(?P<container>[^/]+)/object/' \
'(?P<object_name>.+)$'
# note: not an AJAX request - the body will be raw file content
@csrf_exempt
def post(self, request, container, object_name):
"""Create or replace an object or pseudo-folder
:param request:
:param container:
:param object_name:
If the object_name (ie. POST path) ends in a '/' then a folder is
created, rather than an object. Any file content passed along with
the request will be ignored in that case.
POST parameter:
:param file: the file data for the upload.
:return:
"""
form = UploadObjectForm(request.POST, request.FILES)
if not form.is_valid():
raise rest_utils.AjaxError(500, 'Invalid request')
data = form.clean()
if object_name[-1] == '/':
result = api.swift.swift_create_pseudo_folder(
request,
container,
object_name
)
else:
result = api.swift.swift_upload_object(
request,
container,
object_name,
data['file']
)
return rest_utils.CreatedResponse(
u'/api/swift/containers/%s/object/%s' % (container, result.name)
)
@rest_utils.ajax()
def delete(self, request, container, object_name):
if object_name[-1] == '/':
try:
api.swift.swift_delete_folder(request, container, object_name)
except exceptions.Conflict as e:
# In case the given object is pseudo folder
# It cannot be deleted if it's not empty.
return rest_utils.JSONResponse(str(e), 409)
else:
api.swift.swift_delete_object(request, container, object_name)
def get(self, request, container, object_name):
"""Get the object contents."""
obj = api.swift.swift_get_object(
request,
container,
object_name
)
# Add the original file extension back on if it wasn't preserved in the
# name given to the object.
filename = object_name.rsplit(api.swift.FOLDER_DELIMITER)[-1]
if not os.path.splitext(obj.name)[1] and obj.orig_name:
name, ext = os.path.splitext(obj.orig_name)
filename = "%s%s" % (filename, ext)
response = StreamingHttpResponse(obj.data)
safe = filename.replace(",", "")
if six.PY2:
safe = safe.encode('utf-8')
response['Content-Disposition'] = 'attachment; filename="%s"' % safe
response['Content-Type'] = 'application/octet-stream'
response['Content-Length'] = obj.bytes
return response
@urls.register
class ObjectMetadata(generic.View):
"""API for a single swift object"""
url_regex = r'swift/containers/(?P<container>[^/]+)/metadata/' \
'(?P<object_name>.+)$'
@rest_utils.ajax()
def get(self, request, container, object_name):
return api.swift.swift_get_object(
request,
container_name=container,
object_name=object_name,
with_data=False
).to_dict()
@urls.register
class ObjectCopy(generic.View):
"""API to copy a swift object"""
url_regex = r'swift/containers/(?P<container>[^/]+)/copy/' \
'(?P<object_name>.+)$'
@rest_utils.ajax()
def post(self, request, container, object_name):
dest_container = request.DATA['dest_container']
dest_name = request.DATA['dest_name']
try:
result = api.swift.swift_copy_object(
request,
container,
object_name,
dest_container,
dest_name
)
except exceptions.AlreadyExists as e:
return rest_utils.JSONResponse(str(e), 409)
return rest_utils.CreatedResponse(
u'/api/swift/containers/%s/object/%s' % (dest_container,
result.name)
)
# Copyright 2014, Rackspace, US, Inc.
#
# 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 import urls
urlpatterns = []
# to register the URLs for your API endpoints, decorate the view class with
# @register below, and the import the endpoint module in the
# rest_api/__init__.py module
def register(view):
"""Register API views to respond to a regex pattern.
``url_regex`` on a wrapped view class is used as the regex pattern.
The view should be a standard Django class-based view implementing an
as_view() method. The url_regex attribute of the view should be a standard
Django URL regex pattern.
"""
p = urls.url(view.url_regex, view.as_view())
urlpatterns.append(p)
return view
# Copyright 2014, Rackspace, US, Inc.
#
# 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.
import functools
import json
import logging
from django.conf import settings
from django import http
from django.utils import decorators
from oslo_serialization import jsonutils
from horizon import exceptions
LOG = logging.getLogger(__name__)
class AjaxError(Exception):
def __init__(self, http_status, msg):
self.http_status = http_status
super(AjaxError, self).__init__(msg)
http_errors = exceptions.UNAUTHORIZED + exceptions.NOT_FOUND + \
exceptions.RECOVERABLE + (AjaxError, )
class _RestResponse(http.HttpResponse):
@property
def json(self):
content_type = self['Content-Type']
if content_type != 'application/json':
raise ValueError("content type is %s" % content_type)
return jsonutils.loads(self.content)
class CreatedResponse(_RestResponse):
def __init__(self, location, data=None):
if data is not None:
content = jsonutils.dumps(data, sort_keys=settings.DEBUG)
content_type = 'application/json'
else:
content = ''
content_type = None
super(CreatedResponse, self).__init__(status=201, content=content,
content_type=content_type)
self['Location'] = location
class JSONResponse(_RestResponse):
def __init__(self, data, status=200, json_encoder=json.JSONEncoder):
if status == 204:
content = ''
else:
content = jsonutils.dumps(data, sort_keys=settings.DEBUG,
cls=json_encoder)
super(JSONResponse, self).__init__(
status=status,
content=content,
content_type='application/json',
)
def ajax(authenticated=True, data_required=False,
json_encoder=json.JSONEncoder):
"""Decorator to allow the wrappered view to exist in an AJAX environment.
Provide a decorator to wrap a view method so that it may exist in an
entirely AJAX environment:
- data decoded from JSON as input and data coded as JSON as output
- result status is coded in the HTTP status code; any non-2xx response
data will be coded as a JSON string, otherwise the response type (always
JSON) is specific to the method called.
if authenticated is true then we'll make sure the current user is
authenticated.
If data_required is true then we'll assert that there is a JSON body
present.
The wrapped view method should return either:
- JSON serialisable data
- an object of the django http.HttpResponse subclass (one of JSONResponse
or CreatedResponse is suggested)
- nothing
Methods returning nothing (or None explicitly) will result in a 204 "NO
CONTENT" being returned to the caller.
"""
def decorator(function, authenticated=authenticated,
data_required=data_required):
@functools.wraps(function,
assigned=decorators.available_attrs(function))
def _wrapped(self, request, *args, **kw):
if authenticated and not request.user.is_authenticated():
return JSONResponse('not logged in', 401)
if not request.is_ajax():
return JSONResponse('request must be AJAX', 400)
# decode the JSON body if present
request.DATA = None
if request.body:
try:
request.DATA = jsonutils.loads(request.body)
except (TypeError, ValueError) as e:
return JSONResponse('malformed JSON request: %s' % e, 400)
if data_required:
if not request.DATA:
return JSONResponse('request requires JSON body', 400)
# invoke the wrapped function, handling exceptions sanely
try:
data = function(self, request, *args, **kw)
if isinstance(data, http.HttpResponse):
return data
elif data is None:
return JSONResponse('', status=204)
return JSONResponse(data, json_encoder=json_encoder)
except http_errors as e:
# exception was raised with a specific HTTP status
for attr in ['http_status', 'code', 'status_code']:
if hasattr(e, attr):
http_status = getattr(e, attr)
break
else:
LOG.exception('HTTP exception with no status/code')
return JSONResponse(str(e), 500)
return JSONResponse(str(e), http_status)
except Exception as e:
LOG.exception('error invoking apiclient')
return JSONResponse(str(e), 500)
return _wrapped
return decorator
PARAM_MAPPING = {
'None': None,
'True': True,
'False': False
}
def parse_filters_kwargs(request, client_keywords=None):
"""Extract REST filter parameters from the request GET args.
Client processes some keywords separately from filters and takes
them as separate inputs. This will ignore those keys to avoid
potential conflicts.
"""
filters = {}
kwargs = {}
client_keywords = client_keywords or {}
for param in request.GET:
param_value = PARAM_MAPPING.get(request.GET[param], request.GET[param])
if param in client_keywords:
kwargs[param] = param_value
else:
filters[param] = param_value
return filters, kwargs
def post2data(func):
"""Decorator to restore original form values along with their types.
The sole purpose of this decorator is to restore original form values
along with their types stored on client-side under key $$originalJSON.
This in turn prevents the loss of field types when they are passed with
header 'Content-Type: multipart/form-data', which is needed to pass a
binary blob as a part of POST request.
"""
def wrapper(self, request):
request.DATA = request.POST
if '$$originalJSON' in request.POST:
request.DATA = jsonutils.loads(request.POST['$$originalJSON'])
return func(self, request)
return wrapper
# -*- encoding: utf-8 -*-
# Copyright 2015, Rackspace, US, Inc.
#
# 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.
import re
from six.moves import html_parser
# regex to find filter translation expressions
filter_regex = re.compile(
r"""{\$\s*('([^']|\\')+'|"([^"]|\\")+")\s*\|\s*translate\s*\$}"""
)
# browser innerHTML decodes some html entities automatically, so when
# we extract the msgid and want to match what Javascript sees, we need
# to leave some entities alone, but decode all the rest. Add entries
# to HTML_ENTITIES as necessary.
HTML_ENTITY_PASSTHROUGH = {'amp', 'gt', 'lt'}
HTML_ENTITY_DECODED = {
'reg': u'®',
'times': u'×'
}
class AngularGettextHTMLParser(html_parser.HTMLParser):
"""Parse HTML to find translate directives.
Currently this parses for these forms of translation:
<translate>content</translate>
The content will be translated. Angular value templating will be
recognised and transformed into gettext-familiar translation
strings (i.e. "{$ expression $}" becomes "%(expression)")
<p translate>content</p>
The content will be translated. As above.
{$ 'content' | translate $}
The string will be translated, minus expression handling (i.e. just
bare strings are allowed.)
"""
def __init__(self):
try:
super(AngularGettextHTMLParser, self).__init__(
convert_charrefs=False
)
except TypeError:
# handle HTMLParser not being a type on Python 2
html_parser.HTMLParser.__init__(self)
self.in_translate = False
self.inner_tags = []
self.data = ''
self.strings = []
self.line = 0
self.plural = False
self.plural_form = ''
self.comments = []
def handle_starttag(self, tag, attrs):
self.line = self.getpos()[0]
if tag == 'translate' or \
(attrs and 'translate' in [attr[0] for attr in attrs]):
self.in_translate = True
self.plural_form = ''
for attr, value in attrs:
if attr == 'translate-plural':
self.plural = True
self.plural_form = value
if attr == 'translate-comment':
self.comments.append(value)
elif self.in_translate:
s = tag
if attrs:
s += ' ' + ' '.join('%s="%s"' % a for a in attrs)
self.data += '<%s>' % s
self.inner_tags.append(tag)
else:
for attr in attrs:
if not attr[1]:
continue
for match in filter_regex.findall(attr[1]):
if match:
self.strings.append(
(self.line, u'gettext', match[0][1:-1], [])
)
def handle_data(self, data):
if self.in_translate:
self.data += data
else:
for match in filter_regex.findall(data):
self.strings.append(
(self.line, u'gettext', match[0][1:-1], [])
)
def handle_entityref(self, name):
if self.in_translate:
if name in HTML_ENTITY_PASSTHROUGH:
self.data += '&%s;' % name
else:
self.data += HTML_ENTITY_DECODED[name]
def handle_charref(self, name):
if self.in_translate:
self.data += '&#%s;' % name
def handle_comment(self, comment):
if self.in_translate:
self.data += '<!--%s-->' % comment
def handle_endtag(self, tag):
if self.in_translate:
if len(self.inner_tags) > 0:
tag = self.inner_tags.pop()
self.data += "</%s>" % tag
return
if self.plural_form:
messages = (
self.data.strip(),
self.plural_form
)
func_name = u'ngettext'
else:
messages = self.data.strip()
func_name = u'gettext'
self.strings.append(
(self.line, func_name, messages, self.comments)
)
self.in_translate = False
self.data = ''
self.comments = []
def extract_angular(fileobj, keywords, comment_tags, options):
"""Extract messages from angular template (HTML) files.
It extract messages from angular template (HTML) files that use
angular-gettext translate directive as per
https://angular-gettext.rocketeer.be/
:param fileobj: the file-like object the messages should be extracted
from
:param keywords: This is a standard parameter so it isaccepted but ignored.
:param comment_tags: This is a standard parameter so it is accepted but
ignored.
:param options: Another standard parameter that is accepted but ignored.
:return: an iterator over ``(lineno, funcname, message, comments)``
tuples
:rtype: ``iterator``
"""
parser = AngularGettextHTMLParser()
for line in fileobj:
parser.feed(line)
for string in parser.strings:
yield(string)
# 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 __future__ import division
from csv import DictWriter
from csv import writer
from django.http import HttpResponse
from django.http import StreamingHttpResponse
from django import template as django_template
import six
from six import StringIO
class CsvDataMixin(object):
"""CSV data Mixin - provides handling for CSV data.
.. attribute:: columns
A list of CSV column definitions. If omitted - no column titles
will be shown in the result file. Optional.
"""
def __init__(self):
self.out = StringIO()
super(CsvDataMixin, self).__init__()
if hasattr(self, "columns"):
columns = [self.encode(col) for col in self.columns]
self.writer = DictWriter(self.out, columns)
self.is_dict = True
else:
self.writer = writer(self.out)
self.is_dict = False
def write_csv_header(self):
if self.is_dict:
try:
self.writer.writeheader()
except AttributeError:
# For Python<2.7
self.writer.writerow(dict(zip(
self.writer.fieldnames,
self.writer.fieldnames)))
def write_csv_row(self, args):
if self.is_dict:
self.writer.writerow(dict(zip(
self.writer.fieldnames, [self.encode(col) for col in args])))
else:
self.writer.writerow([self.encode(col) for col in args])
def encode(self, value):
value = six.text_type(value)
if six.PY2:
# csv and StringIO cannot work with mixed encodings,
# so encode all with utf-8
value = value.encode('utf-8')
return value
class BaseCsvResponse(CsvDataMixin, HttpResponse):
"""Base CSV response class. Provides handling of CSV data."""
def __init__(self, request, template, context, content_type, **kwargs):
super(BaseCsvResponse, self).__init__()
self['Content-Disposition'] = 'attachment; filename="%s"' % (
kwargs.get("filename", "export.csv"),)
self['Content-Type'] = content_type
self.context = context
self.header = None
if template:
# Display some header info if provided as a template
header_template = django_template.loader.get_template(template)
self.header = header_template.render(self.context, request)
if self.header:
self.out.write(self.encode(self.header))
self.write_csv_header()
for row in self.get_row_data():
self.write_csv_row(row)
self.out.flush()
self.content = self.out.getvalue()
self.out.close()
def get_row_data(self):
return []
class BaseCsvStreamingResponse(CsvDataMixin, StreamingHttpResponse):
"""Base CSV Streaming class. Provides streaming response for CSV data."""
def __init__(self, request, template, context, content_type, **kwargs):
super(BaseCsvStreamingResponse, self).__init__()
self['Content-Disposition'] = 'attachment; filename="%s"' % (
kwargs.get("filename", "export.csv"),)
self['Content-Type'] = content_type
self.context = context
self.header = None
if template:
# Display some header info if provided as a template
header_template = django_template.loader.get_template(template)
self.header = header_template.render(self.context, request)
self._closable_objects.append(self.out)
self.streaming_content = self.get_content()
def buffer(self):
buf = self.out.getvalue()
self.out.truncate(0)
return buf
def get_content(self):
if self.header:
self.out.write(self.encode(self.header))
self.write_csv_header()
yield self.buffer()
for row in self.get_row_data():
self.write_csv_row(row)
yield self.buffer()
def get_row_data(self):
return []
# Copyright 2016, Rackspace, US, Inc.
#
# 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.
import django.utils.html
def escape(text, existing=django.utils.html.escape):
# Replace our angular markup string with a different string
# (which just happens to be the Django comment string)
# this prevents user-supplied data from being intepreted in
# our pages by angularjs, thus preventing it from being used
# for XSS attacks. Note that we use {$ $} instead of the
# standard {{ }} - this is configured in horizon.framework
# angularjs module through $interpolateProvider.
return existing(text).replace('{$', '{%').replace('$}', '%}')
# this will be invoked as early as possible in settings.py
def monkeypatch_escape():
django.utils.html.escape = escape
# 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.
import logging
from os import path
from os import walk
LOG = logging.getLogger(__name__)
MODULE_EXT = '.module.js'
MOCK_EXT = '.mock.js'
SPEC_EXT = '.spec.js'
def discover_files(base_path, sub_path='', ext='', trim_base_path=False):
"""Discovers all files with certain extension in given paths."""
file_list = []
for root, dirs, files in walk(path.join(base_path, sub_path)):
if trim_base_path:
root = path.relpath(root, base_path)
file_list.extend([path.join(root, file_name)
for file_name in files
if file_name.endswith(ext)])
return sorted(file_list)
def sort_js_files(js_files):
"""Sorts JavaScript files in `js_files`.
It sorts JavaScript files in a given `js_files`
into source files, mock files and spec files based on file extension.
Output:
* sources: source files for production. The order of source files
is significant and should be listed in the below order:
- First, all the that defines the other application's angular module.
Those files have extension of `.module.js`. The order among them is
not significant.
- Followed by all other source code files. The order among them
is not significant.
* mocks: mock files provide mock data/services for tests. They have
extension of `.mock.js`. The order among them is not significant.
* specs: spec files for testing. They have extension of `.spec.js`.
The order among them is not significant.
"""
modules = [f for f in js_files if f.endswith(MODULE_EXT)]
mocks = [f for f in js_files if f.endswith(MOCK_EXT)]
specs = [f for f in js_files if f.endswith(SPEC_EXT)]
other_sources = [f for f in js_files if
not f.endswith(MODULE_EXT)
and not f.endswith(MOCK_EXT)
and not f.endswith(SPEC_EXT)]
sources = modules + other_sources
return sources, mocks, specs
def discover_static_files(base_path, sub_path=''):
"""Discovers static files in given paths.
It returns JavaScript sources, mocks, specs and HTML templates,
all grouped in lists.
"""
js_files = discover_files(base_path, sub_path=sub_path,
ext='.js', trim_base_path=True)
sources, mocks, specs = sort_js_files(js_files)
html_files = discover_files(base_path, sub_path=sub_path,
ext='.html', trim_base_path=True)
p = path.join(base_path, sub_path)
_log(sources, 'JavaScript source', p)
_log(mocks, 'JavaScript mock', p)
_log(specs, 'JavaScript spec', p)
_log(html_files, 'HTML template', p)
return sources, mocks, specs, html_files
def populate_horizon_config(horizon_config, base_path,
sub_path='', prepend=False):
sources, mocks, specs, template = discover_static_files(
base_path, sub_path=sub_path)
if prepend:
horizon_config.setdefault('js_files', [])[:0] = sources
horizon_config.setdefault('js_spec_files', [])[:0] = mocks + specs
horizon_config.setdefault('external_templates', [])[:0] = template
else:
horizon_config.setdefault('js_files', []).extend(sources)
horizon_config.setdefault('js_spec_files', []).extend(mocks + specs)
horizon_config.setdefault('external_templates', []).extend(template)
def _log(file_list, list_name, in_path):
"""Logs result at debug level"""
file_names = '\n'.join(file_list)
LOG.debug("\nDiscovered %(size)d %(name)s file(s) in %(path)s:\n"
"%(files)s\n",
{'size': len(file_list), 'name': list_name, 'path': in_path,
'files': file_names})
# Copyright 2012 Nebula, Inc.
#
# 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.
import datetime
import iso8601
from django.template.defaultfilters import register
from django.template.defaultfilters import timesince
from django.utils.safestring import mark_safe
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
@register.filter
def replace_underscores(string):
return string.replace("_", " ")
@register.filter
def parse_isotime(timestr, default=None):
# This duplicates oslo timeutils parse_isotime but with a
# @register.filter annotation and a silent fallback on error.
try:
return iso8601.parse_date(timestr)
except (iso8601.ParseError, TypeError):
return default or ''
@register.filter
def timesince_or_never(dt, default=None):
"""Call the Django ``timesince`` filter or a given default string.
It returns the string *default* if *dt* is not a valid ``date``
or ``datetime`` object.
When *default* is None, "Never" is returned.
"""
if default is None:
default = _("Never")
if isinstance(dt, datetime.date):
return timesince(dt)
else:
return default
@register.filter
def timesince_sortable(dt):
delta = timezone.now() - dt
# timedelta.total_seconds() not supported on python < 2.7
seconds = delta.seconds + (delta.days * 24 * 3600)
return mark_safe("<span data-seconds=\"%d\">%s</span>" %
(seconds, timesince(dt)))
# 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.
import datetime
import decimal
import math
import re
from oslo_utils import units
import six
from django.conf import settings
from django.contrib.auth import logout
from django import http
from django.utils.encoding import force_text
from django.utils.functional import lazy
from django.utils import translation
def _lazy_join(separator, strings):
return separator.join([force_text(s)
for s in strings])
lazy_join = lazy(_lazy_join, six.text_type)
def bytes_to_gigabytes(bytes):
# Converts the number of bytes to the next highest number of Gigabytes
# For example 5000000 (5 Meg) would return '1'
return int(math.ceil(float(bytes) / units.Gi))
def add_logout_reason(request, response, reason, status='success'):
# Store the translated string in the cookie
lang = translation.get_language_from_request(request)
with translation.override(lang):
reason = six.text_type(reason)
if six.PY2:
reason = reason.encode('utf-8')
response.set_cookie('logout_reason', reason, max_age=10)
response.set_cookie('logout_status', status, max_age=10)
def logout_with_message(request, msg, redirect=True, status='success'):
"""Send HttpResponseRedirect to LOGOUT_URL.
`msg` is a message displayed on the login page after the logout, to explain
the logout reason.
"""
logout(request)
if redirect:
response = http.HttpResponseRedirect(
'%s?next=%s' % (settings.LOGOUT_URL, request.path))
else:
response = http.HttpResponseRedirect(settings.LOGOUT_URL)
add_logout_reason(request, response, msg, status)
return response
def get_config_value(request, key, default, search_in_settings=True):
"""Retrieves the value of `key` from configuration in the following order:
- from the session; if not found there then
- from cookies; if not found there then
- from the settings file if `search_in_settings` is True,
otherwise this step is skipped; if not found there
- `default` is returned
"""
value = request.session.get(key, request.COOKIES.get(key))
if value is None:
if search_in_settings:
value = getattr(settings, key, default)
else:
value = default
if isinstance(default, int):
try:
value = int(value)
except ValueError:
value = request.session[key] = int(default)
return value
def save_config_value(request, response, key, value):
"""Sets value of key `key` to `value` in both session and cookies."""
request.session[key] = value
response.set_cookie(key, value, expires=one_year_from_now())
return response
def get_page_size(request):
return get_config_value(request, 'API_RESULT_PAGE_SIZE', 20)
def get_log_length(request):
return get_config_value(request, 'INSTANCE_LOG_LENGTH', 35)
def get_timezone(request):
# Session and cookie store timezone as django_timezone.
# In case there is no timezone neither in session nor in cookie,
# use default value from settings file where it's called TIME_ZONE.
return get_config_value(request, 'django_timezone',
getattr(settings, 'TIME_ZONE', 'UTC'))
def get_language(request):
return get_config_value(request, settings.LANGUAGE_COOKIE_NAME,
request.LANGUAGE_CODE, search_in_settings=False)
def natural_sort(attr):
return lambda x: [int(s) if s.isdigit() else s for s in
re.split(r'(\d+)', getattr(x, attr, x))]
def get_keys(tuple_of_tuples):
"""Returns a tuple containing first component of each tuple.
It processes a tuple of 2-element tuples and returns a tuple containing
first component of each tuple.
"""
return tuple([t[0] for t in tuple_of_tuples])
def value_for_key(tuple_of_tuples, key):
"""Returns a value containing to the given key.
It processes a tuple of 2-element tuples and returns the value
corresponding to the given key. If no value is found, the key is returned.
"""
for t in tuple_of_tuples:
if t[0] == key:
return t[1]
else:
return key
def next_key(tuple_of_tuples, key):
"""Returns the key which comes after the given key.
It processes a tuple of 2-element tuples and returns the key which comes
after the given key.
"""
for i, t in enumerate(tuple_of_tuples):
if t[0] == key:
try:
return tuple_of_tuples[i + 1][0]
except IndexError:
return None
def previous_key(tuple_of_tuples, key):
"""Returns the key which comes before the give key.
It Processes a tuple of 2-element tuples and returns the key which comes
before the given key.
"""
for i, t in enumerate(tuple_of_tuples):
if t[0] == key:
try:
return tuple_of_tuples[i - 1][0]
except IndexError:
return None
def format_value(value):
"""Returns the given value rounded to one decimal place if deciaml.
Returns the integer if an integer is given.
"""
value = decimal.Decimal(str(value))
if int(value) == value:
return int(value)
# On Python 3, an explicit cast to float is required
return float(round(value, 1))
def one_year_from_now():
now = datetime.datetime.utcnow()
return now + datetime.timedelta(days=365)
# 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.
import copy
from django.forms.utils import flatatt
class HTMLElement(object):
"""A generic base class that gracefully handles html-style attributes."""
def __init__(self):
self.attrs = getattr(self, "attrs", {})
self.classes = getattr(self, "classes", [])
def get_default_classes(self):
"""Returns an iterable of default classes.
They will be combined with any other declared classes.
"""
return []
def get_default_attrs(self):
"""Returns a dict of default attributes.
They will be combined with other declared attributes.
"""
return {}
def get_final_attrs(self, classes=True):
"""Returns a dict containing the final attributes to be rendered."""
final_attrs = copy.copy(self.get_default_attrs())
final_attrs.update(self.attrs)
if classes:
final_attrs['class'] = self.get_final_css()
else:
final_attrs.pop('class', None)
return final_attrs
def get_final_css(self):
"""Returns a final css class concatenated string."""
default = " ".join(self.get_default_classes())
defined = self.attrs.get('class', '')
additional = " ".join(getattr(self, "classes", []))
non_empty = [test for test in (defined, default, additional) if test]
final_classes = " ".join(non_empty).strip()
return final_classes
@property
def attr_string(self):
"""Returns a flattened string of HTML attributes.
HTML attributes are flattened based on the
``attrs`` dict provided to the class.
"""
return flatatt(self.get_final_attrs())
@property
def attr_string_nc(self):
"""Returns a flattened string of HTML attributes.
HTML attributes are flattened based on the
``attrs`` dict provided to the class.
"""
return flatatt(self.get_final_attrs(False))
@property
def class_string(self):
"""Returns a list of class name of HTML Element in string."""
classes_str = " ".join(self.classes)
return classes_str
# Copyright 2016 Red Hat, Inc.
#
# 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.core.serializers.json import DjangoJSONEncoder
from django.utils.encoding import force_text
from django.utils.functional import Promise
class LazyTranslationEncoder(DjangoJSONEncoder):
"""JSON encoder that resolves lazy objects like translations"""
def default(self, obj):
if isinstance(obj, Promise):
return force_text(obj)
return super(LazyTranslationEncoder, self).default(obj)
# Copyright 2012 Nebula, Inc.
#
# 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.
import collections
import functools
import threading
import warnings
import weakref
class UnhashableKeyWarning(RuntimeWarning):
"""Raised when trying to memoize a function with an unhashable argument."""
def _try_weakref(arg, remove_callback):
"""Return a weak reference to arg if possible, or arg itself if not."""
try:
arg = weakref.ref(arg, remove_callback)
except TypeError:
# Not all types can have a weakref. That includes strings
# and floats and such, so just pass them through directly.
pass
return arg
def _get_key(args, kwargs, remove_callback):
"""Calculate the cache key, using weak references where possible."""
# Use tuples, because lists are not hashable.
weak_args = tuple(_try_weakref(arg, remove_callback) for arg in args)
# Use a tuple of (key, values) pairs, because dict is not hashable.
# Sort it, so that we don't depend on the order of keys.
weak_kwargs = tuple(sorted(
(key, _try_weakref(value, remove_callback))
for (key, value) in kwargs.items()))
return weak_args, weak_kwargs
def memoized(func):
"""Decorator that caches function calls.
Caches the decorated function's return value the first time it is called
with the given arguments. If called later with the same arguments, the
cached value is returned instead of calling the decorated function again.
The cache uses weak references to the passed arguments, so it doesn't keep
them alive in memory forever.
"""
# The dictionary in which all the data will be cached. This is a separate
# instance for every decorated function, and it's stored in a closure of
# the wrapped function.
cache = {}
locks = collections.defaultdict(threading.Lock)
@functools.wraps(func)
def wrapped(*args, **kwargs):
# We need to have defined key early, to be able to use it in the
# remove() function, but we calculate the actual value of the key
# later on, because we need the remove() function for that.
key = None
def remove(ref):
"""A callback to remove outdated items from cache."""
try:
# The key here is from closure, and is calculated later.
del cache[key]
del locks[key]
except KeyError:
# Some other weak reference might have already removed that
# key -- in that case we don't need to do anything.
pass
key = _get_key(args, kwargs, remove)
try:
with locks[key]:
try:
# We want cache hit to be as fast as possible, and don't
# really care much about the speed of a cache miss, because
# it will only happen once and likely calls some external
# API, database, or some other slow thing. That's why the
# hit is in straightforward code, and the miss is in an
# exception.
value = cache[key]
except KeyError:
value = cache[key] = func(*args, **kwargs)
except TypeError:
# The calculated key may be unhashable when an unhashable
# object, such as a list, is passed as one of the arguments. In
# that case, we can't cache anything and simply always call the
# decorated function.
warnings.warn(
"The key %r is not hashable and cannot be memoized."
% (key,), UnhashableKeyWarning, 2)
value = func(*args, **kwargs)
return value
return wrapped
# We can use @memoized for methods now too, because it uses weakref and so
# it doesn't keep the instances in memory forever. We might want to separate
# them in the future, however.
memoized_method = memoized
def memoized_with_request(request_func, request_index=0):
"""Decorator for caching functions which receive a request argument
memoized functions with a request argument are memoized only during the
rendering of a single view because the request argument is a new request
instance on each view.
If you want a function to be memoized for multiple views use this
decorator.
It replaces the request argument in the call to the decorated function
with the result of calling request_func on that request object.
request_function is a function which will receive the request argument.
request_index indicates which argument of the decorated function is the
request object to pass into request_func, which will also be replaced
by the result of request_func being called.
your memoized function will instead receive request_func(request)
passed as argument at the request_index.
The intent of that function is to extract the information needed from the
request, and thus the memoizing will operate just on that part of the
request that is relevant to the function being memoized.
short example::
@memoized
def _get_api_client(username, token_id, project_id, auth_url)
return api_client.Client(username, token_id, project_id, auth_url)
def get_api_client(request):
return _api_client(request.user.username,
request.user.token.id,
request.user.tenant_id)
@memoized_with_request(get_api_client)
def some_api_function(api_client, *args, **kwargs):
# is like returning get_api_client(
# request).some_method(*args, **kwargs)
# but with memoization.
return api_client.some_method(*args, **kwargs)
@memoized_with_request(get_api_client, 1)
def some_other_funt(param, api_client, other_param):
# The decorated function will be called this way:
# some_other_funt(param, request, other_param)
# but will be called behind the scenes this way:
# some_other_funt(param, get_api_client(request), other_param)
return api_client.some_method(param, other_param)
See openstack_dashboard.api.nova for a complete example.
"""
def wrapper(func):
memoized_func = memoized(func)
@functools.wraps(func)
def wrapped(*args, **kwargs):
args = list(args)
request = args.pop(request_index)
args.insert(request_index, request_func(request))
return memoized_func(*args, **kwargs)
return wrapped
return wrapper
# (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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 import settings
from django_pyscss.compressor import DjangoScssFilter
from django_pyscss import DjangoScssCompiler
from scss.namespace import Namespace
from scss.types import String
import six
class HorizonScssFilter(DjangoScssFilter):
def __init__(self, *args, **kwargs):
super(HorizonScssFilter, self).__init__(*args, **kwargs)
self.namespace = Namespace()
# Add variables to the SCSS Global Namespace Here
self.namespace.set_variable(
'$static_url',
String(six.text_type(getattr(settings, 'STATIC_URL', '/static/')))
)
# Create a compiler with the right namespace
@property
def compiler(self):
return DjangoScssCompiler(
namespace=self.namespace
)
# Copyright 2012 Nebula, Inc.
#
# 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.
import logging
import os
import random
import string
from oslo_concurrency import lockutils
class FilePermissionError(Exception):
"""The key file permissions are insecure."""
pass
def generate_key(key_length=64):
"""Secret key generator.
The quality of randomness depends on operating system support,
see http://docs.python.org/library/random.html#random.SystemRandom.
"""
if hasattr(random, 'SystemRandom'):
logging.info('Generating a secure random key using SystemRandom.')
choice = random.SystemRandom().choice
else:
msg = "WARNING: SystemRandom not present. Generating a random "\
"key using random.choice (NOT CRYPTOGRAPHICALLY SECURE)."
logging.warning(msg)
choice = random.choice
return ''.join(map(lambda x: choice(string.digits + string.ascii_letters),
range(key_length)))
def read_from_file(key_file='.secret_key'):
if (os.stat(key_file).st_mode & 0o777) != 0o600:
raise FilePermissionError(
"Insecure permissions on key file %s, should be 0600." %
os.path.abspath(key_file))
with open(key_file, 'r') as f:
key = f.readline()
return key
def generate_or_read_from_file(key_file='.secret_key', key_length=64):
"""Multiprocess-safe secret key file generator.
Useful to replace the default (and thus unsafe) SECRET_KEY in settings.py
upon first start. Save to use, i.e. when multiple Python interpreters
serve the dashboard Django application (e.g. in a mod_wsgi + daemonized
environment). Also checks if file permissions are set correctly and
throws an exception if not.
"""
abspath = os.path.abspath(key_file)
# check, if key_file already exists
# if yes, then just read and return key
if os.path.exists(key_file):
key = read_from_file(key_file)
return key
# otherwise, first lock to make sure only one process
lock = lockutils.external_lock(key_file + ".lock",
lock_path=os.path.dirname(abspath))
with lock:
if not os.path.exists(key_file):
key = generate_key(key_length)
old_umask = os.umask(0o177) # Use '0600' file permissions
with open(key_file, 'w') as f:
f.write(key)
os.umask(old_umask)
else:
key = read_from_file(key_file)
return key
# 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.
import six
from django.conf import settings
from django.utils.module_loading import import_string
def import_object(name_or_object):
if isinstance(name_or_object, six.string_types):
return import_string(name_or_object)
return name_or_object
def import_setting(name):
"""Imports an object specified either directly or as a module path."""
value = getattr(settings, name, None)
return import_object(value)
# 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.
import decimal
import pint
from horizon.utils import functions
# Mapping of units from Ceilometer to Pint
INFORMATION_UNITS = (
('B', 'byte'),
('KB', 'Kibyte'),
('MB', 'Mibyte'),
('GB', 'Gibyte'),
('TB', 'Tibyte'),
('PB', 'Pibyte'),
('EB', 'Eibyte'),
)
TIME_UNITS = ('ns', 's', 'min', 'hr', 'day', 'week', 'month', 'year')
ureg = pint.UnitRegistry()
def is_supported(unit):
"""Returns a bool indicating whether the unit specified is supported."""
return unit in functions.get_keys(INFORMATION_UNITS) + TIME_UNITS
def is_larger(unit_1, unit_2):
"""Returns a boolean indicating whether unit_1 is larger than unit_2.
E.g:
>>> is_larger('KB', 'B')
True
>>> is_larger('min', 'day')
False
"""
unit_1 = functions.value_for_key(INFORMATION_UNITS, unit_1)
unit_2 = functions.value_for_key(INFORMATION_UNITS, unit_2)
return ureg.parse_expression(unit_1) > ureg.parse_expression(unit_2)
def convert(value, source_unit, target_unit, fmt=False):
"""Converts value from source_unit to target_unit.
Returns a tuple containing the converted value and target_unit.
Having fmt set to True causes the value to be formatted to 1 decimal digit
if it's a decimal or be formatted as integer if it's an integer.
E.g:
>>> convert(2, 'hr', 'min')
(120.0, 'min')
>>> convert(2, 'hr', 'min', fmt=True)
(120, 'min')
>>> convert(30, 'min', 'hr', fmt=True)
(0.5, 'hr')
"""
orig_target_unit = target_unit
source_unit = functions.value_for_key(INFORMATION_UNITS, source_unit)
target_unit = functions.value_for_key(INFORMATION_UNITS, target_unit)
q = ureg.Quantity(value, source_unit)
q = q.to(ureg.parse_expression(target_unit))
value = functions.format_value(q.magnitude) if fmt else q.magnitude
return value, orig_target_unit
def normalize(value, unit):
"""Converts the value so that it belongs to some expected range.
Returns the new value and new unit.
E.g:
>>> normalize(1024, 'KB')
(1, 'MB')
>>> normalize(90, 'min')
(1.5, 'hr')
>>> normalize(1.0, 'object')
(1, 'object')
"""
if value < 0:
raise ValueError('Negative value: %s %s.' % (value, unit))
if unit in functions.get_keys(INFORMATION_UNITS):
return _normalize_information(value, unit)
elif unit in TIME_UNITS:
return _normalize_time(value, unit)
else:
# Unknown unit, just return it
return functions.format_value(value), unit
def _normalize_information(value, unit):
value = decimal.Decimal(str(value))
while value < 1:
prev_unit = functions.previous_key(INFORMATION_UNITS, unit)
if prev_unit is None:
break
value, unit = convert(value, unit, prev_unit)
while value >= 1024:
next_unit = functions.next_key(INFORMATION_UNITS, unit)
if next_unit is None:
break
value, unit = convert(value, unit, next_unit)
return functions.format_value(value), unit
def _normalize_time(value, unit):
# Normalize time by converting to next higher unit when value is
# at least 2 units
value, unit = convert(value, unit, 's')
if value >= 120:
value, unit = convert(value, 's', 'min')
if value >= 120:
value, unit = convert(value, 'min', 'hr')
if value >= 48:
value, unit = convert(value, 'hr', 'day')
if value >= 730:
value, unit = convert(value, 'day', 'year')
elif value >= 62:
value, unit = convert(value, 'day', 'month')
elif value >= 14:
value, unit = convert(value, 'day', 'week')
return functions.format_value(value), unit
# Copyright 2012 Nebula, Inc.
#
# 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.
import re
from oslo_utils import netutils
from django.core.exceptions import ValidationError
from django.core import validators
from django.utils.translation import ugettext_lazy as _
from horizon import conf
def validate_port_range(port):
if not netutils.is_valid_port(port):
raise ValidationError(_("Not a valid port number"))
def validate_icmp_type_range(icmp_type):
if not netutils.is_valid_icmp_type(icmp_type):
if icmp_type == -1:
return
raise ValidationError(_("Not a valid ICMP type"))
def validate_icmp_code_range(icmp_code):
if not netutils.is_valid_icmp_code(icmp_code):
if icmp_code == -1:
return
raise ValidationError(_("Not a valid ICMP code"))
def validate_ip_protocol(ip_proto):
if ip_proto < -1 or ip_proto > 255:
raise ValidationError(_("Not a valid IP protocol number"))
def password_validator():
return conf.HORIZON_CONFIG["password_validator"]["regex"]
def password_validator_msg():
return conf.HORIZON_CONFIG["password_validator"]["help_text"]
def validate_port_or_colon_separated_port_range(port_range):
"""Accepts a port number or a single-colon separated range."""
if port_range.count(':') > 1:
raise ValidationError(_("One colon allowed in port range"))
ports = port_range.split(':')
for port in ports:
validate_port_range(port)
def validate_metadata(value):
error_msg = _('Invalid metadata entry. Use comma-separated'
' key=value pairs')
if value:
specs = value.split(",")
for spec in specs:
keyval = spec.split("=")
# ensure both sides of "=" exist, but allow blank value
if not len(keyval) == 2 or not keyval[0]:
raise ValidationError(error_msg)
# Same as POSIX [:print:]. Accordingly, diacritics are disallowed.
PRINT_REGEX = re.compile(r'^[\x20-\x7E]*$')
validate_printable_ascii = validators.RegexValidator(
PRINT_REGEX,
_("The string may only contain ASCII printable characters."),
"invalid_characters")
# 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.
class KeystoneAuthException(Exception):
"""Generic error class to identify and catch our own errors."""
pass
# 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.
import collections
import logging
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth import forms as django_auth_forms
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.debug import sensitive_variables
from openstack_auth import exceptions
from openstack_auth import utils
LOG = logging.getLogger(__name__)
class Login(django_auth_forms.AuthenticationForm):
"""Form used for logging in a user.
Handles authentication with Keystone by providing the domain name, username
and password. A scoped token is fetched after successful authentication.
A domain name is required if authenticating with Keystone V3 running
multi-domain configuration.
If the user authenticated has a default project set, the token will be
automatically scoped to their default project.
If the user authenticated has no default project set, the authentication
backend will try to scope to the projects returned from the user's assigned
projects. The first successful project scoped will be returned.
Inherits from the base ``django.contrib.auth.forms.AuthenticationForm``
class for added security features.
"""
use_required_attribute = False
region = forms.ChoiceField(label=_("Region"), required=False)
username = forms.CharField(
label=_("User Name"),
widget=forms.TextInput(attrs={"autofocus": "autofocus"}))
password = forms.CharField(label=_("Password"),
widget=forms.PasswordInput(render_value=False))
def __init__(self, *args, **kwargs):
super(Login, self).__init__(*args, **kwargs)
fields_ordering = ['username', 'password', 'region']
if getattr(settings,
'OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT',
False):
last_domain = self.request.COOKIES.get('login_domain', None)
if getattr(settings,
'OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN',
False):
self.fields['domain'] = forms.ChoiceField(
label=_("Domain"),
initial=last_domain,
required=True,
choices=getattr(settings,
'OPENSTACK_KEYSTONE_DOMAIN_CHOICES',
()))
else:
self.fields['domain'] = forms.CharField(
initial=last_domain,
label=_("Domain"),
required=True,
widget=forms.TextInput(attrs={"autofocus": "autofocus"}))
self.fields['username'].widget = forms.widgets.TextInput()
fields_ordering = ['domain', 'username', 'password', 'region']
self.fields['region'].choices = self.get_region_choices()
if len(self.fields['region'].choices) == 1:
self.fields['region'].initial = self.fields['region'].choices[0][0]
self.fields['region'].widget = forms.widgets.HiddenInput()
elif len(self.fields['region'].choices) > 1:
self.fields['region'].initial = self.request.COOKIES.get(
'login_region')
# if websso is enabled and keystone version supported
# prepend the websso_choices select input to the form
if utils.is_websso_enabled():
initial = getattr(settings, 'WEBSSO_INITIAL_CHOICE', 'credentials')
self.fields['auth_type'] = forms.ChoiceField(
label=_("Authenticate using"),
choices=getattr(settings, 'WEBSSO_CHOICES', ()),
required=False,
initial=initial)
# add auth_type to the top of the list
fields_ordering.insert(0, 'auth_type')
# websso is enabled, but keystone version is not supported
elif getattr(settings, 'WEBSSO_ENABLED', False):
msg = ("Websso is enabled but horizon is not configured to work " +
"with keystone version 3 or above.")
LOG.warning(msg)
self.fields = collections.OrderedDict(
(key, self.fields[key]) for key in fields_ordering)
@staticmethod
def get_region_choices():
default_region = (settings.OPENSTACK_KEYSTONE_URL, "Default Region")
regions = getattr(settings, 'AVAILABLE_REGIONS', [])
if not regions:
regions = [default_region]
return regions
@sensitive_variables()
def clean(self):
default_domain = getattr(settings,
'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN',
'Default')
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
region = self.cleaned_data.get('region')
domain = self.cleaned_data.get('domain', default_domain)
if not (username and password):
# Don't authenticate, just let the other validators handle it.
return self.cleaned_data
try:
self.user_cache = authenticate(request=self.request,
username=username,
password=password,
user_domain_name=domain,
auth_url=region)
LOG.info('Login successful for user "%(username)s" using domain '
'"%(domain)s", remote address %(remote_ip)s.',
{'username': username, 'domain': domain,
'remote_ip': utils.get_client_ip(self.request)})
except exceptions.KeystoneAuthException as exc:
LOG.info('Login failed for user "%(username)s" using domain '
'"%(domain)s", remote address %(remote_ip)s.',
{'username': username, 'domain': domain,
'remote_ip': utils.get_client_ip(self.request)})
raise forms.ValidationError(exc)
if hasattr(self, 'check_for_test_cookie'): # Dropped in django 1.7
self.check_for_test_cookie()
return self.cleaned_data
# Robert Simai <robert.simai@suse.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-01-31 06:40+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-12-08 01:54+0000\n"
"Last-Translator: Robert Simai <robert.simai@suse.com>\n"
"Language-Team: German\n"
"Language: de\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "An error occurred authenticating. Please try again later."
msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noch einmal."
msgid "Authenticate using"
msgstr "Authentifizieren mit"
msgid "Domain"
msgstr "Domäne"
msgid "Invalid credentials."
msgstr "Unzureichende Berechtigung."
msgid "K2K Federation not setup for this session"
msgstr "K2K Verbund ist für diese Sitzung nicht eingerichtet"
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr ""
"Es konnte kein Authentifizierungsbackend für die angegebene Legitimierung "
"gefunden werden."
msgid "Password"
msgstr "Passwort"
#, python-format
msgid "Please consider changing your password, it will expire in %s minutes"
msgstr "Bitte ändern Sie Ihr Passwort. Es läuft in %s Minuten ab."
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "Projektumschaltung für Benutzer \"%(username)s\" fehlgeschlagen."
msgid "Region"
msgstr "Region"
#, python-format
msgid "Service provider authentication failed. %s"
msgstr "Dienstanbieter Authentifizierung fehlgeschlagen. %s"
#, python-format
msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
msgstr ""
"Umschalten zum Keystone Anbieter \"%(keystone_provider)s\" erfolgreich."
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "Umschalten zum Projekt \"%(project_name)s\" erfolgreich."
msgid "The authentication token issued by the Identity service has expired."
msgstr ""
"Das vom Identitätsdienst ausgegebene Authentifizierungs-Token ist abgelaufen."
msgid "Unable to establish connection to keystone endpoint."
msgstr "Es kann keine Verbindung zum Keystone Endpunkt aufgebaut werden."
msgid "Unable to retrieve authorized domains."
msgstr "Autorisierte Domänen können nicht abgerufen werden."
msgid "Unable to retrieve authorized projects."
msgstr "Autorisierte Projekte können nicht abgerufen werden."
msgid "User Name"
msgstr "Benutzername"
msgid "You are not authorized for any projects or domains."
msgstr "Sie sind nicht autorisiert für irgendein Projekt oder eine Domäne."
msgid "You are not authorized for any projects."
msgstr "Sie sind für kein Projekt berechtigt."
# Andi Chandler <andi@gowling.com>, 2017. #zanata
# Andi Chandler <andi@gowling.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-02-01 07:31+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-02-01 01:04+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en-GB\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "An error occurred authenticating. Please try again later."
msgstr "An error occurred authenticating. Please try again later."
msgid "Authenticate using"
msgstr "Authenticate using"
msgid "Could not find service provider ID on keystone."
msgstr "Could not find service provider ID on Keystone."
msgid "Domain"
msgstr "Domain"
msgid "Identity provider authentication failed."
msgstr "Identity provider authentication failed."
msgid "Invalid credentials."
msgstr "Invalid credentials."
msgid "K2K Federation not setup for this session"
msgstr "K2K Federation not setup for this session"
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgid "Password"
msgstr "Password"
#, python-format
msgid "Please consider changing your password, it will expire in %s minutes"
msgstr "Please consider changing your password, it will expire in %s minutes"
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "Project switch failed for user \"%(username)s\"."
msgid "Region"
msgstr "Region"
#, python-format
msgid "Service provider authentication failed. %s"
msgstr "Service provider authentication failed. %s"
#, python-format
msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
msgstr "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "Switch to project \"%(project_name)s\" successful."
msgid "The authentication token issued by the Identity service has expired."
msgstr "The authentication token issued by the Identity service has expired."
msgid "Unable to establish connection to keystone endpoint."
msgstr "Unable to establish connection to Keystone endpoint."
msgid "Unable to retrieve authorized domains."
msgstr "Unable to retrieve authorised domains."
msgid "Unable to retrieve authorized projects."
msgstr "Unable to retrieve authorised projects."
msgid "User Name"
msgstr "User Name"
msgid "You are not authorized for any projects or domains."
msgstr "You are not authorised for any projects or domains."
msgid "You are not authorized for any projects."
msgstr "You are not authorised for any projects."
# Georg Hennemann <georg.hennemann@t-systems.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-01-31 06:40+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-11-28 03:29+0000\n"
"Last-Translator: Georg Hennemann <georg.hennemann@t-systems.com>\n"
"Language-Team: Esperanto\n"
"Language: eo\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "An error occurred authenticating. Please try again later."
msgstr "Eraro okazis dum aŭtentigado. Bonvolu provu denove poste."
msgid "Authenticate using"
msgstr "Aŭtentigu uzante"
msgid "Domain"
msgstr "Domajno"
msgid "Invalid credentials."
msgstr "Maljusta legitimaĵo."
msgid "K2K Federation not setup for this session"
msgstr "K2K Federacio ne establita por tiu seanco"
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr "Nebla difini aŭtentiga servo por trakti la provizitajn legitimaĵojn."
msgid "Password"
msgstr "Pasvorto"
#, python-format
msgid "Please consider changing your password, it will expire in %s minutes"
msgstr "Bonvolu konsideru ŝanĝi vian pasvorton, ĝi malvalidas en %s minutoj"
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "Projekto ŝanĝo malsukcesis por uzanto \"%(username)s\"."
msgid "Region"
msgstr "Regiono"
#, python-format
msgid "Service provider authentication failed. %s"
msgstr "Servo provizanto aŭtentigado malsukcesis. %s"
#, python-format
msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
msgstr "Ŝanĝo al Keystone Provizanto \"%(keystone_provider)s\"sukcesis."
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "Ŝanĝi al projekto \"%(project_name)s\" sukcesis."
msgid "The authentication token issued by the Identity service has expired."
msgstr "The authentication token issued by the Identity service has expired."
msgid "Unable to establish connection to keystone endpoint."
msgstr "Neebla konekti al keystone finpunkto."
msgid "Unable to retrieve authorized domains."
msgstr "Neebla trovi rajtigitaj domajnoj."
msgid "Unable to retrieve authorized projects."
msgstr "Neebla trovi rajtigitaj projektoj."
msgid "User Name"
msgstr "Uzanto Nomo"
msgid "You are not authorized for any projects or domains."
msgstr "Vi ne estas rajtigita pri iujn projektojn au domajnojn."
msgid "You are not authorized for any projects."
msgstr "Vi ne estas rajtigita pri iujn projektojn."
# Alberto Molina Coballes <alb.molina@gmail.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-02-01 07:31+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-01-31 06:49+0000\n"
"Last-Translator: Alberto Molina Coballes <alb.molina@gmail.com>\n"
"Language-Team: Spanish\n"
"Language: es\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "An error occurred authenticating. Please try again later."
msgstr ""
"Ha ocurrido un error en la autenticación. Por favor, inténtelo más tarde."
msgid "Authenticate using"
msgstr "Autenticar mediante"
msgid "Could not find service provider ID on keystone."
msgstr "No se puede encontrar el ID del proveedor en keystone."
msgid "Domain"
msgstr "Dominio"
msgid "Identity provider authentication failed."
msgstr "Ha fallado la autenticación del proveedor de identidad."
msgid "Invalid credentials."
msgstr "Credenciales no válidas."
msgid "K2K Federation not setup for this session"
msgstr "Se ha configurado una federación K2K en esta sesión"
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr ""
"No se puede determinar el sistema de autenticación a utilizar para manejar "
"las credenciales proporcionadas."
msgid "Password"
msgstr "Contraseña"
#, python-format
msgid "Please consider changing your password, it will expire in %s minutes"
msgstr "Por favor, considere cambiar su contraseña, expirará en %s minutos"
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "Ha fallado el cambio de proyecto para el usuario \"%(username)s\"."
msgid "Region"
msgstr "Región"
#, python-format
msgid "Service provider authentication failed. %s"
msgstr "Ha fallado la autenticación del proveedor del servicio. %s"
#, python-format
msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
msgstr ""
"Se ha cambiado al proveedor de keystone \"%(keystone_provider)s\" "
"correctamente."
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "Se ha cambiado al proyecto \"%(project_name)s\" correcamente."
msgid "The authentication token issued by the Identity service has expired."
msgstr "Ha caducado el token de autenticación del servicio de identidad."
msgid "Unable to establish connection to keystone endpoint."
msgstr "No es posible establecer una conexión con el endpoint de keystone."
msgid "Unable to retrieve authorized domains."
msgstr "No ha sido posible obtener dominios autorizados."
msgid "Unable to retrieve authorized projects."
msgstr "No ha sido posible obtener proyectos autorizados."
msgid "User Name"
msgstr "Usuario"
msgid "You are not authorized for any projects or domains."
msgstr "No está autorizado en ningún proyecto o dominio."
msgid "You are not authorized for any projects."
msgstr "No está autorizado en ningún proyecto."
# Loic Nicolle <loic.nicolle@orange.com>, 2017. #zanata
# JF Taltavull <jftalta@gmail.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-01-31 06:40+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-01-09 08:30+0000\n"
"Last-Translator: JF Taltavull <jftalta@gmail.com>\n"
"Language-Team: French\n"
"Language: fr\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
msgid "An error occurred authenticating. Please try again later."
msgstr ""
"Une erreur s'est produite pendant l'authentification. Veuillez réessayer "
"ultérieurement."
msgid "Authenticate using"
msgstr "Mode d'authentification"
msgid "Domain"
msgstr "Domaine"
msgid "Invalid credentials."
msgstr "Informations d'identification non valides."
msgid "K2K Federation not setup for this session"
msgstr "La fédération K2K n'a pas été configurée pour cette session"
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr ""
"Aucun backend d'authentification pour gérer les informations "
"d'authentification fournies."
msgid "Password"
msgstr "Mot de passe"
#, python-format
msgid "Please consider changing your password, it will expire in %s minutes"
msgstr "Merci de changer votre mot de passe, il va expirer dans %s minutes"
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "La bascule de projet a échoué pour l'utilisateur \"%(username)s\"."
msgid "Region"
msgstr "Région"
#, python-format
msgid "Service provider authentication failed. %s"
msgstr "L'authentification du fournisseur de services a échoué. %s"
#, python-format
msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
msgstr ""
"Bascule vers le fournisseur Keystone \"%(keystone_provider)s\" réussie."
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "Bascule vers le projet \"%(project_name)s\" réussie. "
msgid "The authentication token issued by the Identity service has expired."
msgstr ""
"Le jeton d'authentification délivré par le service d'Identité a expiré."
msgid "Unable to establish connection to keystone endpoint."
msgstr "Impossible d'établir la connexion avec le endpoint keystone."
msgid "Unable to retrieve authorized domains."
msgstr "Impossible de récupérer les domaines autorisés."
msgid "Unable to retrieve authorized projects."
msgstr "Impossible de récupérer les projets autorisés."
msgid "User Name"
msgstr "Nom d'utilisateur"
msgid "You are not authorized for any projects or domains."
msgstr "Vous n'êtes autorisé sur aucun projet ou domaine."
msgid "You are not authorized for any projects."
msgstr "Vous n'êtes autorisé sur aucun projet."
# suhartono <cloudsuhartono@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-01-31 06:40+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-11-15 04:45+0000\n"
"Last-Translator: suhartono <cloudsuhartono@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "An error occurred authenticating. Please try again later."
msgstr "Terjadi kesalahan saat melakukan otentikasi. Silakan coba lagi nanti."
msgid "Authenticate using"
msgstr "Otentikasi menggunakan"
msgid "Domain"
msgstr "Domain"
msgid "Invalid credentials."
msgstr "Kredensial tidak valid."
msgid "K2K Federation not setup for this session"
msgstr "K2K Federation tidak disiapkan untuk sesi ini"
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr ""
"Tidak ada backend otentikasi yang bisa ditentukan untuk menangani kredensial "
"yang diberikan."
msgid "Password"
msgstr "Password"
#, python-format
msgid "Please consider changing your password, it will expire in %s minutes"
msgstr ""
"Mohon pertimbangkan untuk mengubah kata sandi Anda, maka akan kedaluwarsa "
"dalam %s minutes"
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "Peralihan proyek gagal bagi pengguna \"%(username)s\"."
msgid "Region"
msgstr "Region"
#, python-format
msgid "Service provider authentication failed. %s"
msgstr "Otentikasi penyedia layanan gagal. %s"
#, python-format
msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
msgstr "Beralih ke Keystone Provider \"%(keystone_provider)s\" berhasil."
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "Beralih ke proyek \"%(project_name)s\" berhasil."
msgid "The authentication token issued by the Identity service has expired."
msgstr ""
"Token otentikasi yang dikeluarkan oleh layanan Identity telah kadaluarsa."
msgid "Unable to establish connection to keystone endpoint."
msgstr "Tidak dapat menjalin koneksi ke keystone endpoint."
msgid "Unable to retrieve authorized domains."
msgstr "Tidak dapat mengambil domain resmi."
msgid "Unable to retrieve authorized projects."
msgstr "Tidak dapat mengambil proyek resmi"
msgid "User Name"
msgstr "User Name"
msgid "You are not authorized for any projects or domains."
msgstr "Anda tidak berwenang untuk proyek atau domain apa pun."
msgid "You are not authorized for any projects."
msgstr "Anda tidak berwenang untuk proyek apa pun."
# Yuko Katabami <yukokatabami@gmail.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-02-07 19:44+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-02-07 09:55+0000\n"
"Last-Translator: Akihiro Motoki <amotoki@gmail.com>\n"
"Language-Team: Japanese\n"
"Language: ja\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "An error occurred authenticating. Please try again later."
msgstr "認証中にエラーが発生しました。後ほどもう一度お試しください。"
msgid "Authenticate using"
msgstr "使用する認証方法"
msgid "Could not find service provider ID on keystone."
msgstr "keystone でサービスプロバイダー ID が見つかりません。"
msgid "Domain"
msgstr "ドメイン"
msgid "Identity provider authentication failed."
msgstr "認証プロバイダーの認証に失敗しました。"
msgid "Invalid credentials."
msgstr "認証情報が無効です。"
msgid "K2K Federation not setup for this session"
msgstr "このセッションには K2K フェデレーションは設定されていません。"
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr "指定された認証情報を処理する認証バックエンドが決定できませんでした。"
msgid "Password"
msgstr "パスワード"
#, python-format
msgid "Please consider changing your password, it will expire in %s minutes"
msgstr ""
"パスワードの変更をお勧めします。今のパスワードはあと %s 分で無効になります。"
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "ユーザー \"%(username)s\" のプロジェクト切り替えに失敗しました。"
msgid "Region"
msgstr "リージョン"
#, python-format
msgid "Service provider authentication failed. %s"
msgstr "サービスプロバイダーの認証に失敗しました。%s"
#, python-format
msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
msgstr ""
"Keystone プロバイダー \"%(keystone_provider)s\" への切り替えが正常に完了しま"
"した。"
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "プロジェクト \"%(project_name)s\" へ正常に切り替えました。"
msgid "The authentication token issued by the Identity service has expired."
msgstr "Identity サービスにより発行された認証トークンの期限が切れました。"
msgid "Unable to establish connection to keystone endpoint."
msgstr "Keystone エンドポイントへの接続を確立できません。"
msgid "Unable to retrieve authorized domains."
msgstr "認証されたドメインを取得できません。"
msgid "Unable to retrieve authorized projects."
msgstr "権限を持っているプロジェクトの情報を取得できません。"
msgid "User Name"
msgstr "ユーザー名"
msgid "You are not authorized for any projects or domains."
msgstr "どのプロジェクトやドメインに対しても権限がありません。"
msgid "You are not authorized for any projects."
msgstr "どのプロジェクトに対しても権限がありません。"
# ByungYeol Woo <wby1089@gmail.com>, 2017. #zanata
# Ian Y. Choi <ianyrchoi@gmail.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-02-07 19:44+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-02-07 02:14+0000\n"
"Last-Translator: Ian Y. Choi <ianyrchoi@gmail.com>\n"
"Language-Team: Korean (South Korea)\n"
"Language: ko-KR\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "An error occurred authenticating. Please try again later."
msgstr "인증 도중 오류가 발생했습니다. 잠시 후 다시 시도하세요."
msgid "Authenticate using"
msgstr "인증 사용"
msgid "Could not find service provider ID on keystone."
msgstr "keystone 에서 서비스 프로바이더 ID 를 찾지 못하였습니다."
msgid "Domain"
msgstr "도메인"
msgid "Identity provider authentication failed."
msgstr "Identity 프로바이더 인증에 실패했습니다."
msgid "Invalid credentials."
msgstr "올바르지 않은 자격증명."
msgid "K2K Federation not setup for this session"
msgstr "이 세션에는 K2K 페더레이션이 설정되지 않았습니다."
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr "제공된 자격증명을 처리할 수 있는 인증 백엔드를 정할 수 없습니다."
msgid "Password"
msgstr "비밀번호"
#, python-format
msgid "Please consider changing your password, it will expire in %s minutes"
msgstr "암호는 %s 분 후에 만료되므로 변경해 주시기 바랍니다."
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "사용자 \"%(username)s\"의 프로젝트 전환이 실패했습니다."
msgid "Region"
msgstr "지역"
#, python-format
msgid "Service provider authentication failed. %s"
msgstr "서비스 제공자 인증이 실패됨. %s"
#, python-format
msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
msgstr "키스톤 제공자 \"%(keystone_provider)s\" 로의 변경이 성공하였습니다."
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "프로젝트 \"%(project_name)s\" 로 전환에 성공하였습니다."
msgid "The authentication token issued by the Identity service has expired."
msgstr "Identity 서비스에서 발급한 인증 토큰이 만료되었습니다."
msgid "Unable to establish connection to keystone endpoint."
msgstr "키스톤 엔드포인트에 연결할 수 없습니다."
msgid "Unable to retrieve authorized domains."
msgstr "인가된 도메인을 가져올 수 없습니다."
msgid "Unable to retrieve authorized projects."
msgstr "인가된 프로젝트를 가져올 수 없습니다."
msgid "User Name"
msgstr "사용자 이름"
msgid "You are not authorized for any projects or domains."
msgstr "모든 프로젝트나 도메인에 대한 권한이 없습니다."
msgid "You are not authorized for any projects."
msgstr "모든 프로젝트에 대한 권한이 없습니다."
# Fernando Pimenta <fernando.c.pimenta@gmail.com>, 2018. #zanata
# Marcelo Dieder <marcelodieder@gmail.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-02-01 07:31+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-02-01 01:54+0000\n"
"Last-Translator: Marcelo Dieder <marcelodieder@gmail.com>\n"
"Language-Team: Portuguese (Brazil)\n"
"Language: pt-BR\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "An error occurred authenticating. Please try again later."
msgstr ""
"Um erro ocorreu na autenticação. Por favor, tente novamente mais tarde."
msgid "Authenticate using"
msgstr "Autenticar utilizando"
msgid "Could not find service provider ID on keystone."
msgstr "Não foi possível localizar o ID do provedor do serviço no keystone."
msgid "Domain"
msgstr "Domínio"
msgid "Identity provider authentication failed."
msgstr "A autenticação do provedor de identidade falhou."
msgid "Invalid credentials."
msgstr "Credenciais inválidas"
msgid "K2K Federation not setup for this session"
msgstr "Federação K2K não configurada para esta sessão"
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr ""
"Nenhum backend de autenticação pode ser determinado para lidar com as "
"credenciais fornecidas."
msgid "Password"
msgstr "Senha"
#, python-format
msgid "Please consider changing your password, it will expire in %s minutes"
msgstr "Por favor, considere alterar sua senha, ela irá expirar em %s minutos."
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "Troca de projeto falhou para o usuário \"%(username)s\"."
msgid "Region"
msgstr "Região"
#, python-format
msgid "Service provider authentication failed. %s"
msgstr "Autenticação do provedor de serviços falhou. %s"
#, python-format
msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
msgstr ""
"Troca para o provedor Keystone \"%(keystone_provider)s\" realizada com "
"sucesso."
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "Troca para o projeto \"%(project_name)s\" com sucesso."
msgid "The authentication token issued by the Identity service has expired."
msgstr "O token de autenticação emitido pelo serviço de Identidade expirou."
msgid "Unable to establish connection to keystone endpoint."
msgstr "Não é possível estabelecer conexão com o endpoint do Keystone."
msgid "Unable to retrieve authorized domains."
msgstr "Não é possível recuperar domínios autorizados."
msgid "Unable to retrieve authorized projects."
msgstr "Não é possível recuperar projetos autorizados."
msgid "User Name"
msgstr "Nome de Usuário"
msgid "You are not authorized for any projects or domains."
msgstr "Você não está autorizado para nenhum projeto ou domínio."
msgid "You are not authorized for any projects."
msgstr "Você não está autorizado para nenhum projeto."
# TigerFang <tigerfun@126.com>, 2017. #zanata
# Tony <tfu@redhat.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-02-07 05:35+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-02-06 12:19+0000\n"
"Last-Translator: Tony <tfu@redhat.com>\n"
"Language-Team: Chinese (China)\n"
"Language: zh-CN\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "An error occurred authenticating. Please try again later."
msgstr "验证过程中发生错误。请稍后重试。"
msgid "Authenticate using"
msgstr "用于认证"
msgid "Could not find service provider ID on keystone."
msgstr "在 keystone 中无法找到服务提供者。"
msgid "Domain"
msgstr "域"
msgid "Identity provider authentication failed."
msgstr "身份提供者身份验证失败。"
msgid "Invalid credentials."
msgstr "证书不可用。"
msgid "K2K Federation not setup for this session"
msgstr "当前会话中没有设置K2K联盟。"
msgid ""
"No authentication backend could be determined to handle the provided "
"credentials."
msgstr "对于提供的证书,无法找到对应的认证后端来进行处理。"
msgid "Password"
msgstr "密码"
#, python-format
msgid "Please consider changing your password, it will expire in %s minutes"
msgstr "您的密码将于%s后过期,请及时修改"
#, python-format
msgid "Project switch failed for user \"%(username)s\"."
msgstr "用户\"%(username)s\"切换项目失败。"
msgid "Region"
msgstr "区域"
#, python-format
msgid "Service provider authentication failed. %s"
msgstr "服务提供者验证失败。 %s"
#, python-format
msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful."
msgstr "成功切换至Keystone提供者\"%(keystone_provider)s\"。"
#, python-format
msgid "Switch to project \"%(project_name)s\" successful."
msgstr "成功切换到\"%(project_name)s\"项目。"
msgid "The authentication token issued by the Identity service has expired."
msgstr "认证服务中使用的验证令牌已经过期。"
msgid "Unable to establish connection to keystone endpoint."
msgstr "无法与keystone服务终端建立连接。"
msgid "Unable to retrieve authorized domains."
msgstr "无法获取授权的域。"
msgid "Unable to retrieve authorized projects."
msgstr "无法获取授权的项目。"
msgid "User Name"
msgstr "用户名"
msgid "You are not authorized for any projects or domains."
msgstr "您没有任何授权的项目或者域。"
msgid "You are not authorized for any projects."
msgstr "您没有任何授权的项目。"
# 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.
# import the User model in here so Django can find it
from openstack_auth.user import User
__all__ = ['User']
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment